Accessibilité frontend
L'accessibilité fait partie intégrante de toute modification d'interface utilisateur — conçue, développée et testée avec elle. Une tâche UI n'est terminée que si elle fonctionne uniquement au clavier, que chaque contrôle annonce son nom, son rôle et son état, que les vérifications automatisées réussissent et que les tests interrogent par rôle.
Objectif : WCAG 2.2 niveau AA — dans l'UE, l'Acte Européen sur l'Accessibilité (lois nationales telles que BFSG, EN 301 549) le rend obligatoire pour de nombreux produits. Une constatation cite un critère WCAG A/AA ou une tâche concrète qu'un utilisateur de technologie d'assistance ne peut pas accomplir. Les critères et conventions AAA (un <h1>, des cibles de 44 px) sont des recommandations, jamais des bloqueurs.
| Tâche | Lire |
|---|---|
| Choix des éléments, points de repère, titres, ARIA, noms accessibles, états désactivés | references/semantics-and-aria.md |
| Anneaux de focus, ordre de tabulation, lien d'évitement, boîtes de dialogue, changements de route, touches de raccourci des widgets | references/focus-and-keyboard.md |
| Étiquettes, saisie semi-automatique, types d'entrée, erreurs de validation, comportement de soumission | references/forms.md |
| Annonce des changements, régions live, toasts, texte alternatif, SVG, médias | references/screen-readers.md |
| Contraste, couleur, zones cibles, mouvement, zoom, réaffichage, gestion du contenu | references/visual-and-motion.md |
| Tests automatisés et manuels, CI | references/testing.md |
| Audit/examen de l'UI existante, format du rapport | references/audit.md |
| Recherche d'un critère WCAG 2.2 | references/wcag.md |
Règles fondamentales — toujours
Priorité au natif. <button> pour les actions, <a href> / <Link> du routeur pour la navigation, <label>, <fieldset>, <table>, <dialog>, points de repère. Jamais `». Pas d'ARIA est mieux qu'un mauvais ARIA ; un rôle promet le modèle clavier complet. Les widgets complexes (combobox, menu, sélecteur de date) proviennent de la bibliothèque headless accessible du projet, ne sont pas construits à la main.
Chaque élément interactif a un nom accessible [4.1.2]. Les boutons avec uniquement une icône reçoivent aria-label, l'icône aria-hidden="true". Le texte de l'étiquette visible fait partie du nom accessible [2.5.3]. Les liens indiquent où ils mènent — pas de « cliquez ici » [2.4.4].
Clavier [2.1.1, 2.1.2]. Tout ce qui fonctionne avec un pointeur fonctionne avec le clavier ; pas de pièges. L'ordre de tabulation = ordre visuel ; jamais un tabIndex positif. Échap ferme la superposition la plus haute. Le glissement et les gestes ont une alternative à pointeur unique [2.5.7, 2.5.1].
Focus visible [2.4.7, 1.4.11]. Styliser :focus-visible ; jamais outline: none sans un remplacement vérifié (≥ 3:1 par rapport à chaque couleur adjacente). Le focus n'est pas caché derrière des en-têtes/bannières fixés [2.4.11] — scroll-padding-top.
Gestion du focus. Boîtes de dialogue : le focus entre à l'ouverture, est piégé, l'arrière-plan est inerte, retourne au déclencheur à la fermeture. Changement de route SPA : mettre à jour document.title [2.4.2] et déplacer le focus vers le nouveau <h1> (tabIndex={-1}) ou <main>.
Formulaires [1.3.1, 3.3.1, 3.3.2]. <label> visible pour chaque contrôle — un espace réservé n'est pas une étiquette. type, inputMode, autoComplete corrects [1.3.5]. Erreurs en ligne, liées avec aria-describedby, champ aria-invalid="true", focus sur le premier champ invalide à la soumission. Valider à la soumission — ne pas désactiver la soumission tant que ce n'est pas valide. Ne jamais bloquer le collage [3.3.8].
Annoncer les changements dynamiques [4.1.3]. Spécifique au champ → aria-describedby ; non urgent (sauvegardé, nombre de résultats, chargement) → role="status" ; erreur urgente au niveau du formulaire → role="alert". La région live existe dans le DOM avant que son texte ne change.
Couleur et contraste [1.4.3, 1.4.11, 1.4.1]. Texte ≥ 4.5:1 (grand ≥ 3:1), limites UI, icônes et anneaux de focus ≥ 3:1 — appliqués via des jetons de conception. La couleur n'est jamais le seul indicateur.
Zones cibles [2.5.8]. ≥ 24×24 px CSS ou espacement suffisant ; viser 40 px bureau / 44 px tactile. Les superpositions décoratives reçoivent pointer-events: none.
Mouvement et temps [2.3.3, 2.2.2]. Animations opt-in via @media (prefers-reduced-motion: no-preference) ; défilement parallèle/lecture automatique désactivés en mouvement réduit. Tout mouvement de plus de 5 s peut être mis en pause. Les toasts avec actions ou erreurs restent jusqu'à ce qu'ils soient rejetés.
Zoom et réaffichage [1.4.4, 1.4.10]. Fonctionne à 200 % de la taille du texte et à 320 px de largeur CSS sans défilement horizontal. Jamais maximum-scale=1 / user-scalable=no.
Bases de la page. <html lang> défini et mis à jour avec la langue de l'UI [3.1.1] ; un seul <main> visible ; les titres forment un plan ; lien d'évitement en premier lorsque la navigation précède <main> [2.4.1].
Images. Significative → alt décrit le but ; décorative → alt="" ; fonctionnelle (l'image est le lien/bouton) → l'action [1.1.1].
Devoir de test
Chaque modification UI est livrée avec :
- des tests de composants qui interrogent par rôle et nom (
getByRole) et couvrent les parcours clavier (user.tab(),{Escape},toHaveFocus()), - des stories Storybook dont la vérification addon-a11y s'exécute en tant qu'erreur (échec CI),
- des analyses Playwright +
@axe-core/playwright(balises WCAG 2.x A/AA) sur les pages et états critiques, - une passe manuelle uniquement au clavier du flux ; une passe avec lecteur d'écran (VoiceOver/NVDA) pour les flux critiques.
Les outils automatisés ne trouvent qu'une partie des problèmes — une analyse axe propre n'est pas une conformité WCAG. Détails : references/testing.md.
Avant de terminer — passez en revue la modification pour
| Motif | Correctif |
|---|---|
<div onClick> / <span onClick> | <button>, ou <a href> quand il navigue |
role="button"/role="tab" sans gestion des touches | élément natif ou les touches de raccourci APG |
outline: none / outline-none sans remplacement focus-visible | conserver l'anneau du navigateur ou un anneau personnalisé vérifié |
tabIndex positif | corriger l'ordre du DOM, utiliser 0 / -1 |
<img sans alt | alt="" ou une description du but |
| bouton icône sans nom | aria-label, icône aria-hidden |
champ de saisie sans <label> / espace réservé comme étiquette | étiquette liée visible |
disabled={!isValid} sur la soumission | garder activé, valider à la soumission |
{msg && <div role="status">…} | rendre la région vide d'abord, mettre à jour son texte |
aria-live="assertive" sur les messages de succès | role="status" |
aria-hidden sur ou au-dessus d'un élément focusable | le supprimer ou rendre le sous-arbre inert |
onPaste + preventDefault | autoriser le collage |
maximum-scale=1 / user-scalable=no | supprimer |
en-tête fixe sans scroll-padding-top | ajouter un espacement au défileur |
| glissement/geste sans alternative bouton | ajouter un chemin à pointeur unique |
transition: all, mouvement sans protection mouvement réduit | lister les propriétés, encapsuler dans no-preference |
Rapports
Les audits et examens sont rapportés dans la langue de la conversation, une constatation par cause racine avec tous les emplacements, le critère WCAG, l'impact sur l'utilisateur et le correctif concret — format dans references/audit.md. La sévérité correspond à la compétence de révision de code : HIGH → Bloker/Majeur, MEDIUM → Majeur/Mineur, LOW → Mineur.
Sources
Combiné à partir de : Addy Osmani's web-quality-skills accessibility (MIT), Jakub Krehel's better-accessibility (MIT), ibelick's fixing-accessibility (MIT), Vercel's Web Interface Guidelines (MIT) ; W3C WCAG 2.2 et WAI-ARIA Authoring Practices.