Skip to Content

Chapitre 15.1 — Concevoir accessible

⏱️ TL;DR — La plupart des problèmes d’accessibilité ne naissent pas dans le code : ils naissent dans la maquette. Un contraste trop faible, un état de focus « oublié », une information portée par la seule couleur, une cible de clic minuscule — tout ça est décidé au design, et tu ne fais que le transcrire. Concevoir accessible (le « shift-left »), c’est intégrer ces contraintes dès les design tokens et les specs, pour ne plus jamais les rattraper à la fin. Corriger un contraste dans une maquette prend 5 minutes ; le corriger sur 40 écrans déjà codés prend une semaine.

🎯 Objectifs

  • Comprendre le principe du shift-left : traiter l’a11y en amont, pas en correction finale.
  • Intégrer le contraste dans les design tokens de couleur, pas au cas par cas.
  • Exiger que le focus, les états (erreur, survol, désactivé, chargement, vide) et les cibles soient définis dans la maquette.
  • Ne jamais coder une information par la couleur seule.
  • Faire de la relation dev / designer une collaboration a11y, pas un ping-pong de bugs.

Le coût d’un problème dépend de quand on le trouve

Un défaut d’accessibilité coûte de moins en moins cher à corriger plus tôt on le détecte :

La leçon : le design est le meilleur endroit pour gagner de l’accessibilité. Le contraste, la taille de texte, l’ordre visuel, les états — ce sont des décisions de conception. Si elles arrivent accessibles dans ton Figma, ton code l’est presque gratuitement.

Le contraste vit dans les tokens de couleur

Le piège classique : le designer choisit une palette « à l’œil », et on découvre au test que le gris sur blanc ne passe pas les 4.5:1 exigés (texte normal, WCAG niveau AA — voir Texte, couleur & contraste). Solution : valider le contraste une fois, au niveau des design tokens, et n’utiliser que des paires validées.

/* ❌ Couleurs posées à l'œil, contraste jamais vérifié */ .badge { color: #9aa0a6; /* gris clair */ background: #f1f3f4; /* ~1.9:1 → illisible, échoue AA */ } /* ✅ Tokens dont les paires texte/fond sont validées ≥ 4.5:1 */ :root { --color-text: #1f2933; /* sur --surface : ~13:1 */ --color-text-muted: #52606d; /* sur --surface : ~7:1 */ --color-surface: #ffffff; --color-danger-text: #b3261e; /* sur --surface : ~5.9:1 */ } .badge { color: var(--color-text-muted); background: var(--color-surface); }

Documente, à côté de chaque token, sur quel fond il est garanti lisible. Un token de couleur sans son ratio de contraste est une information incomplète.

💡 Réflexe — Ne raisonne jamais « quelle couleur ? » mais « quelle paire texte / fond, et à quel ratio ? ». Le contraste n’est pas une propriété d’une couleur isolée : c’est une propriété d’un couple. Range tes tokens par paires validées et interdis les combinaisons libres. Tu élimines des dizaines de non-conformités d’un coup.

Le focus fait partie du design, pas du hasard

Trop de maquettes ne montrent que l’état « au repos ». Le navigateur fournit un focus par défaut ; les resets CSS le suppriment souvent (outline: none), et personne ne le redessine. Résultat : une interface inutilisable au clavier visible (voir Clavier & focus).

Exige que la maquette définisse un style de focus réutilisable, porté par un token, et applique-le partout via :focus-visible.

:root { --focus-ring: 0 0 0 3px #1a73e8; /* anneau visible, contrasté sur le fond */ } /* ✅ Un anneau de focus cohérent sur tous les éléments interactifs */ :where(a, button, input, select, textarea, [tabindex]):focus-visible { outline: none; box-shadow: var(--focus-ring); }

L’information ne passe jamais par la couleur seule

Un champ en erreur juste bordé de rouge, un statut « en ligne » juste en pastille verte, un lien juste en bleu sans soulignement : une personne daltonienne (ou en plein soleil) ne perçoit rien. Ajoute toujours un second signal : icône, texte, forme, soulignement.

{/* ❌ L'erreur n'existe que par la couleur du contour */} <input className="input input--error" /> {/* ✅ Couleur + icône + message texte relié au champ */} <input aria-invalid="true" aria-describedby="email-err" className="input input--error" /> <p id="email-err" className="error"> <IconAlert aria-hidden="true" /> E-mail invalide : il manque un « @ ». </p>

Prévoir tous les états dès la maquette

Un composant n’a pas un seul visage. Le designer doit livrer, et toi réclamer, chaque état — sinon tu improvises, et l’improvisation est rarement accessible.

ÉtatCe qu’il faut prévoir côté a11y
Repos / survolSurvol ne doit pas être le seul indice (voir focus).
Focus clavierAnneau visible et contrasté (:focus-visible).
DésactivéContraste suffisant du texte, raison indiquée.
ErreurCouleur + icône + message relié (aria-describedby).
ChargementÉtat annoncé (aria-busy, texte « Chargement… »).
VideMessage clair expliquant quoi faire, pas un écran blanc.

Cibles de clic et typographie

  • Taille de cible : vise des cibles confortables (l’ordre de 44 × 44 px est une bonne cible tactile ; WCAG 2.2 introduit un minimum sur les cibles — détaillé en Mobile, responsive & mouvement). Décidé au design via l’espacement et le padding des composants.
  • Typographie : taille de base lisible (autour de 16 px pour le corps), interlignage généreux, texte redimensionnable sans casse jusqu’à 200 %. Pas de texte gris pâle « pour faire épuré ».
  • Espacement : l’air entre les éléments réduit la charge visuelle — c’est aussi de l’accessibilité cognitive (chapitre suivant).

⚠️ Piège — « On rendra ça accessible plus tard, à la fin. » Le « plus tard » n’arrive jamais : la a11y devient une montagne de rattrapage qu’aucun sprint n’absorbe. Un contraste ou un focus rattrapé après coup, c’est du refactoring sur du code figé, avec régressions à la clé. Le seul moment économique pour décider l’accessibilité, c’est avant de coder.

🧭 Sur A11yLearn — Avant, le designer d’A11yLearn livrait des maquettes « au repos » et l’équipe découvrait les trous en recette. On a changé le contrat : la bibliothèque Figma d’A11yLearn ne contient plus que des tokens de couleur pré-validés (chaque paire texte/fond annotée de son ratio), un token de focus unique, et chaque composant est livré avec ses six états (repos, focus, désactivé, erreur, chargement, vide). Résultat : les tickets « contraste insuffisant » et « focus invisible » ont quasiment disparu de la recette — ils sont réglés à la source, dans la maquette.

La collaboration dev / designer

L’a11y n’est ni « le problème du dev » ni « celui du designer » : c’est une responsabilité partagée avec un partage clair.

  • Le designer décide : palette et contrastes, hiérarchie visuelle, style de focus, tous les états, tailles de cible, langage.
  • Le dev garantit : sémantique HTML, ordre du DOM, focus au clavier, annonces aux lecteurs d’écran, respect fidèle des specs.
  • Ensemble : une revue de maquette où le dev pose les questions a11y avant le développement (« l’erreur, c’est quoi le message et l’icône ? », « le focus, il ressemble à quoi ici ? »). Cinq minutes en amont valent une semaine de tickets en aval.

✏️ Exercices

Exercice 1 — Repère les décisions de design manquantes. On te donne une maquette d’un champ « Code promo » : un input et un bouton « Appliquer ». Le designer n’a fourni que l’état au repos. Liste les états et informations qui manquent pour que tu puisses coder accessible.

✅ Solution

Manquent au minimum : (1) le focus clavier (à quoi ressemble l’anneau sur l’input et le bouton) ; (2) l’état désactivé du bouton tant que le champ est vide, avec un contraste texte suffisant ; (3) l’état erreur (code invalide) — couleur + icône + message, et le texte exact du message ; (4) l’état chargement pendant la vérification (« Vérification… », aria-busy) ; (5) l’état succès (« Code appliqué : -10 % »). Sans ces specs, tu improvises, et l’improvisation passe rarement l’audit. Réclame-les avant de coder.

Exercice 2 — Corrige la palette. Un designer te donne : texte #8a8f98 sur fond #ffffff pour les libellés secondaires, et une erreur signalée uniquement par une bordure #e53935. Que corriges-tu, et comment le figes-tu pour que ça ne se reproduise pas ?

✅ Solution

Le gris #8a8f98 sur blanc tourne autour de 3:1 : il échoue les 4.5:1 exigés pour du texte normal (AA). Il faut l’assombrir (par ex. vers #52606d, ~7:1) et le stocker comme token --color-text-muted annoté de son ratio sur --color-surface. Pour l’erreur, la couleur seule ne suffit pas : ajoute une icône et un message texte relié au champ par aria-describedby, plus aria-invalid="true". Pour éviter la récidive : n’autoriser que des paires de tokens validées, et vérifier le contraste automatiquement (voir Tester l’accessibilité).

🧠 Quiz de révision

1. Qu’est-ce que le « shift-left » appliqué à l’accessibilité ?

Déplacer le traitement de l’a11y le plus en amont possible du cycle — dès la maquette et les design tokens — au lieu de la corriger en recette ou après la mise en prod. Plus un défaut est détecté tôt, moins il coûte à corriger.

2. Pourquoi valider le contraste au niveau des tokens plutôt que par écran ?

Parce que le contraste est une propriété d’une paire texte/fond, pas d’une couleur isolée. En ne validant que des paires de tokens réutilisées partout, tu élimines des dizaines de non-conformités d’un coup, au lieu de les traquer écran par écran.

3. Pourquoi ne jamais coder une information par la seule couleur ?

Parce qu’une personne daltonienne, malvoyante ou dans de mauvaises conditions de lumière ne la percevra pas. Il faut toujours un second signal : icône, texte, forme ou soulignement, en plus de la couleur (WCAG « Utilisation de la couleur »).

4. Cite trois états qu’une maquette doit fournir au-delà du « repos ».

Parmi : focus clavier, désactivé, erreur (couleur + icône + message), chargement, vide. Sans ces specs, le dev improvise, et l’improvisation est rarement accessible.

5. Qui décide du style de focus, et où doit-il être défini ?

Le designer le décide (comme un choix de conception à part entière), et il doit être défini dans la maquette puis porté par un token unique réutilisé via :focus-visible, plutôt que laissé au hasard des resets CSS.


Chapitre suivant : Un design system accessible — comment un composant partagé transforme « corriger une fois » en « accessible partout ».

Last updated on