Skip to Content

Chapitre 15.2 — Un design system accessible

⏱️ TL;DR — Le design system est ton plus gros levier d’accessibilité. Si le Button, l’Input, la Modal et les Tabs partagés sont accessibles une fois pour toutes, chaque produit qui les consomme hérite de l’accessibilité gratuitement : tu corriges à un endroit, tu bénéficies partout. La contrepartie est réelle : un composant partagé bugué propage son défaut sur tous les écrans. D’où deux exigences : chaque composant doit documenter son comportement a11y (clavier, ARIA, états), et être testé en continu (Storybook + addon a11y). Le design system, c’est l’accessibilité qui passe à l’échelle.

🎯 Objectifs

  • Voir le design system comme le point de correction unique de l’accessibilité.
  • Intégrer clavier, ARIA et états dans les composants partagés, pas dans chaque page.
  • Documenter le contrat a11y de chaque composant (clavier, rôles, états).
  • Utiliser Storybook + addon a11y pour tester chaque composant isolément.
  • Mesurer le risque inverse : un composant partagé défaillant propage le bug à grande échelle.

Le levier : corriger une fois, bénéficier partout

Sans design system, chaque équipe recode son bouton, sa modale, son onglet — et chacun refait les mêmes erreurs d’accessibilité. Avec un design system, il existe un seul Button, une seule Modal. Rendre ce composant accessible profite instantanément à tous ses consommateurs.

C’est le renversement d’échelle : l’a11y cesse d’être un travail répété sur chaque page pour devenir une propriété du composant. Un développeur qui consomme <Modal> n’a plus à connaître le piège du focus ou l’attribut aria-modal — il en hérite.

Encapsuler l’accessibilité dans le composant

Les patterns complexes (voir Composants interactifs) sont exactement ce qu’il faut encapsuler. Le consommateur écrit du code simple ; toute la mécanique a11y vit à l’intérieur.

{/* ❌ Sans design system : chaque page recâble la modale à la main (et oublie le piège de focus, Échap, aria-modal, le retour du focus…) */} <div className="overlay"> <div className="dialog"> <h2>Confirmer</h2> ... </div> </div> {/* ✅ Le composant partagé porte tout le contrat a11y en interne */} <Modal isOpen={open} onClose={close} titleId="confirm-title"> <h2 id="confirm-title">Confirmer</h2> ... </Modal>

À l’intérieur de Modal, une fois pour toutes : role="dialog", aria-modal="true", le libellé via aria-labelledby, le piégeage du focus, la fermeture par Échap, et le retour du focus sur l’élément déclencheur à la fermeture. Le consommateur n’a rien à savoir de tout ça — c’est le sens même d’un composant partagé.

Chaque composant a un contrat d’accessibilité documenté

Un composant partagé non documenté sera mal utilisé. Documente, à côté de chaque composant, son contrat a11y : quelles interactions clavier il gère, quels rôles/attributs ARIA il pose, quels états il expose, et ce que le consommateur doit fournir (un libellé, un id…).

ComposantClavierARIA poséCe que le consommateur fournit
ButtonEntrée / Espace activenttype correct ; aria-disabled si besoinUn libellé explicite (ou aria-label si icône seule)
InputSaisie nativearia-invalid, aria-describedby reliéslabel associé, message d’erreur
ModalÉchap ferme, focus piégérole="dialog", aria-modal, aria-labelledbyUn titre + son id
TabsFlèches naviguent, Home/Endrole="tablist/tab/tabpanel", aria-selectedLibellés d’onglets

Cette table est le mode d’emploi a11y de ta bibliothèque. Sans elle, un dev collera un <Button> avec une icône seule et sans libellé — le composant est accessible, son usage ne l’est pas.

⚠️ Piège — Le composant est accessible, mais mal utilisé. Un IconButton parfait reste muet pour un lecteur d’écran si le consommateur oublie l’aria-label. Le design system doit rendre le bon usage évident : rends le libellé obligatoire dans les props (TypeScript qui refuse le composant sans aria-label quand il n’y a pas d’enfant textuel), documente-le, et vérifie-le en test. L’accessibilité d’un composant partagé, c’est aussi celle de son API.

Le risque inverse : un bug qui se propage partout

Le levier joue dans les deux sens. Si la Modal partagée cesse de rendre le focus au déclencheur, ce n’est pas une page cassée : ce sont toutes les modales de tous les produits. La centralisation démultiplie autant les corrections que les régressions.

Conséquences pratiques :

  • Les composants du design system méritent le plus haut niveau de tests a11y — ce sont eux qui portent le risque systémique.
  • Toute modification d’un composant partagé passe par une revue a11y et une non-régression automatisée.
  • Versionne et communique : un changement de comportement clavier de Tabs est un breaking change d’accessibilité, pas un détail.

Storybook + addon a11y : tester chaque composant isolément

Storybook rend chaque composant visible dans tous ses états, isolé de l’application. L’addon a11y (basé sur le moteur axe) analyse automatiquement chaque story et signale les violations (contraste, rôles manquants, libellés absents) — directement dans l’atelier du développeur.

  • Une story par état : repos, focus, désactivé, erreur, chargement, vide (les états du chapitre précédent).
  • L’addon a11y tourne sur chacune → le dev voit les violations avant même d’intégrer le composant.
  • On automatise ensuite ces vérifications en CI (voir Tester l’accessibilité) : une story qui régresse casse la build.

Storybook devient ainsi à la fois la documentation vivante du contrat a11y et son banc de test.

💡 Réflexe — Quand tu croises un bug d’accessibilité dans une page, pose-toi toujours la question : « est-ce que ça vient d’un composant partagé ? » Si oui, ne le corrige pas dans la page — remonte la correction dans le design system. Tu répares le symptôme et tous les autres endroits où il se manifeste (souvent sans le savoir). Corriger dans la page, c’est traiter une fuite sans fermer le robinet.

🧭 Sur A11yLearn — A11yLearn a extrait sa bibliothèque de composants dans un paquet @a11ylearn/ui, documenté dans un Storybook public. Chaque composant (Button, Field, Modal, Tabs, Toast) y est livré avec ses six états, sa table de contrat a11y, et l’addon a11y actif sur chaque story. Le jour où l’on a corrigé le retour de focus de la Modal, la correction s’est propagée à l’app Next.js et aux blocs intégrés dans Moodle en une seule PR. C’est là que « corriger une fois, bénéficier partout » cesse d’être un slogan : c’est devenu la mécanique quotidienne de l’équipe.

✏️ Exercices

Exercice 1 — Rédige le contrat a11y d’un Tooltip. Ton design system ajoute un composant Tooltip (bulle d’aide au survol/focus d’un élément). Décris son contrat d’accessibilité : clavier, ARIA, et ce que le consommateur doit fournir.

✅ Solution

Clavier : le tooltip doit apparaître au focus de l’élément déclencheur, pas seulement au survol souris, et se fermer avec Échap sans déplacer le focus. ARIA : le déclencheur porte aria-describedby pointant vers l’id de la bulle (le tooltip décrit l’élément) ; la bulle a role="tooltip". Contenu : texte court, jamais interactif (pas de lien/bouton dans un tooltip — sinon c’est une popover, autre pattern). À fournir par le consommateur : le texte d’aide et un déclencheur déjà focusable. Le composant garantit le reste. Le point clé souvent raté : focus, pas seulement survol — sinon inaccessible au clavier.

Exercice 2 — Fuite ou robinet ? En recette, on te signale que sur trois écrans différents, le message d’erreur d’un formulaire n’est pas annoncé au lecteur d’écran. Les trois utilisent le même composant <Field>. Que fais-tu, concrètement ?

✅ Solution

Trois écrans, un seul composant : le bug est presque certainement dans <Field>, pas dans les pages. Je ne patche pas les trois écrans — je remonte dans le composant partagé : vérifier que <Field> relie bien le message d’erreur au champ (aria-describedby vers l’id du message, aria-invalid="true") et éventuellement l’annonce (région live). Une PR sur <Field>, une story Storybook « état erreur » + test a11y pour verrouiller la non-régression, et les trois écrans — plus tous ceux que je n’ai pas vus — sont réparés d’un coup. Fermer le robinet, pas éponger la fuite.

🧠 Quiz de révision

1. Pourquoi le design system est-il le principal levier d’accessibilité ?

Parce qu’il centralise les composants : rendre le Button ou la Modal partagés accessibles une fois profite à tous les produits qui les consomment. L’a11y devient une propriété du composant réutilisable, au lieu d’un travail répété sur chaque page.

2. Qu’est-ce que le « contrat d’accessibilité » d’un composant ?

La documentation de son comportement a11y : quelles interactions clavier il gère, quels attributs ARIA il pose, quels états il expose, et ce que le consommateur doit fournir (libellé, id…). Sans ce contrat, un composant accessible est mal utilisé.

3. Quel est le risque inverse de la centralisation ?

Un composant partagé bugué propage son défaut à tous les écrans qui l’utilisent. La même mécanique qui démultiplie les corrections démultiplie les régressions — d’où l’exigence de tests a11y renforcés et de non-régression sur ces composants.

4. À quoi sert l’addon a11y de Storybook ?

À analyser automatiquement chaque story (avec le moteur axe) et signaler les violations d’accessibilité — contraste, rôles, libellés manquants — dans l’atelier isolé du composant, avant son intégration. Storybook sert alors de documentation vivante et de banc de test a11y.

5. Tu vois un bug d’a11y dans une page. Quel réflexe ?

Vérifier s’il provient d’un composant partagé. Si oui, remonter la correction dans le design system plutôt que la patcher dans la page — tu répares le symptôme et tous les autres endroits concernés d’un coup (fermer le robinet, pas éponger la fuite).


Chapitre suivant : Accessibilité cognitive — la famille de handicaps la plus large, qui se traite par la clarté plus que par l’ARIA.

Last updated on