Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
24 mars 2026

« No ARIA is better than bad ARIA » : les 7 trucs que je vire quand une UI devient injouable

Le mauvais ARIA ne rend pas une UI plus accessible. Il la rend plus imprévisible. Voilà 7 patterns que je supprime (ou que je refais) en priorité, avec des fixes concrets.

12 min de lecture
29 vues
réactions
Partager :
« No ARIA is better than bad ARIA » : les 7 trucs que je vire quand une UI devient injouable

Si tu as déjà entendu « No ARIA is better than bad ARIA », ce n’est pas une punchline de puriste. C’est un constat de prod. Du mauvais ARIA, ce n’est pas juste “inutile” : ça change le comportement pour les lecteurs d’écran, ça casse le clavier, ça ment sur ce qu’est un composant, et ça finit en UI injouable.

Ce que je propose ici est volontairement pratique. Je te donne les 7 patterns que je supprime en premier quand je tombe sur un design system “accessibilisé” à coups d’attributs. Pour chacun : ce que ça fait côté clavier/AT, comment je le reproduis vite, et le fix qui tient (spoiler : souvent c’est juste du HTML sémantique).

« No ARIA… » : la vraie règle, c’est “HTML d’abord, ARIA ensuite”

ARIA n’a jamais été conçu pour remplacer le HTML. Il sert à compléter quand tu n’as pas d’élément natif qui fait le job, ou quand tu construis un widget complexe (combobox, tree, grid…). Le problème, c’est qu’en équipe on l’utilise souvent comme un pansement : on garde un <div> cliquable, et on colle role="button". On masque des trucs au lecteur d’écran “parce que ça gêne”. On remplace un label par un aria-label posé au hasard. Résultat : l’UI devient incohérente selon le mode d’usage.

Mon heuristique est simple : si un attribut ARIA ne vient pas décrire un état réel ou relier un nom accessible à un contrôle, je pars du principe qu’il est suspect. Et si un composant “marche à la souris” mais pas au clavier, je considère que c’est cassé, même si Lighthouse te met une médaille.

1) role="button" sur un <div> : tu as créé un faux bouton

C’est probablement le pattern n°1 que je vois. Un <div role="button"> cliquable à la souris, parfois avec un tabindex="0", et tout le monde est content. Sauf que le clavier n’est pas un détail : un vrai bouton se déclenche à Entrée et à Espace, gère un état disabled, expose un rôle stable, et a un focus correct par défaut.

Symptôme terrain : tu tabules jusqu’au “bouton”, tu appuies sur Espace, et… rien. Ou pire, Espace scroll la page. Côté lecteur d’écran, le contrôle est annoncé comme bouton, mais il ne se comporte pas comme un bouton. C’est exactement le genre de truc qui fait perdre confiance.

Fix : tu remets un <button>. Si tu as besoin d’un lien, tu utilises un <a href>. Si tu veux un style particulier, tu styles l’élément natif au lieu d’inventer un faux.

2) aria-hidden="true" utilisé comme une gomme : tu masques trop (parfois l’essentiel)

aria-hidden est utile, mais c’est une arme. Le cas classique : un composant a une icône déco, donc quelqu’un met aria-hidden="true"… sur le mauvais conteneur. Et d’un coup, tu as un bouton visible, cliquable, mais “inexistant” pour un lecteur d’écran. Autre grand classique : aria-hidden sur un wrapper global (header, main, voire body) pour “faire taire” des annonces parasites. Ça, c’est souvent une réaction à un autre bug… et tu payes en accessibilité globale.

Symptôme terrain : au clavier tu arrives sur un focus “fantôme” (tu vois le focus, mais le lecteur d’écran n’annonce rien), ou tu ne peux plus atteindre un bloc entier au lecteur d’écran alors qu’il est visible. Pour reproduire vite : NVDA/VoiceOver + navigation par titres/landmarks. Si la structure disparaît, il y a un aria-hidden toxique quelque part.

Fix : ne mets aria-hidden que sur des éléments réellement décoratifs (souvent une icône) et seulement si tu sais pourquoi tu le fais. Si tu essayes de masquer un contenu parce qu’il est “visuellement caché”, regarde plutôt hidden, display:none ou une classe sr-only bien faite selon l’intention. Et pour les modales, évite les hacks “je cache tout le reste” à l’ARIA : gère correctement le focus (on y revient plus bas).

3) aria-label qui réécrit le texte visible : tu changes le “nom accessible” sans t’en rendre compte

Un bouton avec le texte « Télécharger » et un aria-label="Download file" parce que quelqu’un a copié un exemple en anglais. Ou un champ avec un placeholder visible, mais un aria-label différent “pour être sûr”. C’est sournois : tu penses “j’ajoute de l’accessibilité”, en réalité tu fabriques une UI où ce que voit l’utilisateur ne correspond plus à ce qui est annoncé.

Symptôme terrain : les gens qui utilisent un lecteur d’écran + vision partielle (oui, ça arrive beaucoup) entendent autre chose que ce qu’ils voient. Et même sans ça, tes tests QA deviennent incohérents : tu demandes « clique Télécharger », l’AT annonce « Download file ». Bonjour la confusion.

Fix : dans la majorité des cas, tu n’as pas besoin de aria-label si ton texte visible est déjà clair. Et si ton contrôle a un label visible, tu le laisses définir le nom accessible. aria-label est utile pour des boutons icône sans texte, ou des cas où le label visible est volontairement incomplet, mais il doit rester aligné avec l’UI.

4) aria-labelledby (et id) en vrac : références cassées, doublons, boucles

Le aria-labelledby est puissant… donc très facile à casser. Dans un design system, j’ai déjà vu des composants qui génèrent des id non uniques, des aria-labelledby qui pointent vers un élément pas rendu, et même des chaînes du type « A est labellisé par B » et « B est labellisé par A ». Les navigateurs/AT font au mieux, mais ce “au mieux” varie.

Symptôme terrain : un champ est annoncé sans nom, ou avec un nom incompréhensible (« blank », ou une concat bizarre). Pour reproduire : ouvre l’arbre d’accessibilité dans les DevTools (Chrome/Edge), regarde le “Name” du contrôle, et vérifie que l’élément référencé existe, est unique, et n’est pas masqué.

Fix : dès que tu es sur un champ de formulaire, le duo gagnant reste <label for> + <input id>. Pour une zone plus complexe, garde aria-labelledby mais traite-le comme du code critique : ids uniques, stables, testés. Et si tu peux t’en passer, passe-t’en. Moins de tuyauterie, moins de surprises.

5) tabindex partout (ou pire, positif) : tu as cassé l’ordre de navigation

Quand je vois tabindex="0" collé sur des divs “parce que sinon on ne peut pas focus”, je sais que je vais trouver des composants qui se comportent mal. Et le tabindex positif (tabindex="1", 2, etc.), c’est presque toujours une dette. Tu crées un ordre de tabulation artificiel qui ne suit plus le DOM, qui casse dès qu’un bloc apparaît/disparaît, et qui devient impossible à maintenir.

Symptôme terrain : tu tabules et tu “téléportes” le focus dans la page. Ou tu dois faire 25 Tabs pour sortir d’un header. Ou tu te retrouves coincé dans un composant qui a rendu focusables des éléments qui ne devraient pas l’être (des wrappers, des cards entières, des sections…).

Fix : reviens aux éléments focusables natifs. Si un élément doit être interactif, c’est probablement qu’il devrait être un <button> ou un <a>, pas un conteneur focusable. Et si tu as un vrai composant composite (menu, listbox), le focus management doit être pensé, pas bricolé avec des tabindex au hasard.

6) aria-disabled sans vrai disabled : visuellement “grisé”, techniquement cliquable

Ça, c’est un grand moment de solitude : le bouton a l’air désactivé, il annonce “disabled” au lecteur d’écran… mais il reçoit le focus et déclenche quand même l’action. Ou alors il est “disabled” à l’ARIA mais tu as laissé un handler click qui part quand même. ARIA ne bloque rien, il décrit. Si tu décris “désactivé” mais que tu laisses l’action possible, tu mens.

Symptôme terrain : au clavier, tu peux activer un truc “désactivé”. Côté lecteur d’écran, tu entends “indisponible” mais le composant réagit quand même. En QA, c’est simple : Tab sur le bouton, essaye Espace/Entrée. Si ça déclenche, c’est un bug.

Fix : sur un vrai <button>, tu utilises disabled. Sur un lien, tu ne fais pas semblant de “désactiver” un lien : tu changes le comportement produit (pas de navigation), tu retires le href si nécessaire, et tu gères un style cohérent. aria-disabled peut être utile pour certains widgets custom, mais il ne remplace pas une désactivation fonctionnelle.

7) Modales et menus “ARIA” : si le focus n’est pas cadré, c’est non

Les modales et menus sont le terrain de jeu préféré du mauvais ARIA. Tu vois role="dialog", aria-modal="true", deux-trois labels… et aucune gestion du focus. Résultat : à l’ouverture, le focus reste derrière. À la fermeture, tu reviens en haut de page. Et au clavier, tu peux tabuler sur le contenu de fond alors qu’une modale est “ouverte”. Côté lecteur d’écran, c’est vite l’enfer : tu entends la modale, puis tu tombes sur un lien du footer “derrière”.

Fix pragmatique : si tu peux utiliser <dialog> (support correct aujourd’hui, avec un polyfill si tu vises large), fais-le. Sinon, traite la modale comme un composant d’interaction, pas comme un overlay visuel. À l’ouverture, focus sur un élément logique dans la modale. Pendant la modale, tu confines la tabulation. À la fermeture, tu redonnes le focus au déclencheur. Et tu ne “bricoles” pas ça avec des aria-hidden globaux posés au hasard.

<!-- Faux bouton (à éviter) -->
<div role="button" tabindex="0" onclick="save()">Enregistrer</div>

<!-- Vrai bouton (à faire) -->
<button type="button" onclick="save()">Enregistrer</button>

<!-- Label natif : simple, robuste -->
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" />

Mon mini process de QA quand je “nettoie” de l’ARIA

Je ne lance pas une batterie de tests parfaite. Je veux juste savoir si l’UI est jouable. Je commence au clavier : Tab / Shift+Tab, puis Entrée et Espace sur tout ce qui ressemble à un bouton, et je vérifie que le focus est visible et logique. Si une modale s’ouvre, je vérifie immédiatement le focus à l’ouverture et le retour du focus à la fermeture.

Ensuite je fais un passage lecteur d’écran basique, sans prétendre être expert AT. Sur Windows, NVDA suffit pour repérer 80% des gros mensonges (contrôles sans nom, rôles incohérents, éléments “invisibles” à l’AT). Sur macOS/iOS, VoiceOver te montrera vite les modales qui laissent fuiter le fond, et les labels qui partent en sucette. Et si tu veux une vérif rapide sans AT, l’arbre d’accessibilité dans Chrome DevTools est déjà un bon détecteur de dégâts, surtout sur le “Name” et le “Role”.

Ce que je supprime sans regret (et ce que je garde)

Je supprime sans trembler tout ce qui “fait semblant” : des rôles collés sur des divs pour imiter un élément natif, des tabindex partout pour rendre focusable un truc qui ne devrait pas l’être, des aria-label qui réécrivent le texte visible, des aria-hidden globaux utilisés comme cache-misère. La majorité du temps, le correctif est un retour à des briques HTML simples, plus faciles à tester et plus stables dans le temps.

Je garde l’ARIA quand il sert vraiment à décrire un état dynamique ou une relation utile. Un bouton icône sans texte, oui. Un composant riche qui n’a pas d’équivalent natif, oui, mais avec un vrai travail clavier/AT. Et surtout : je le garde quand l’équipe a décidé d’en être responsable, avec des tests et une QA minimale. Sinon, c’est juste du bruit.

Conclusion : le meilleur “fix a11y”, c’est souvent de retirer du code

Si tu retiens une idée : l’accessibilité ne vient pas d’un empilement d’attributs. Elle vient d’une UI qui se comporte de façon fiable, quel que soit le mode d’entrée. Quand une interface devient injouable, je commence presque toujours par enlever le mauvais ARIA, remettre du HTML sémantique, et réparer le focus. C’est moins glamour que d’ajouter des rôles partout, mais c’est ce qui tient en prod.

Et si tu es en train de construire un design system : considère l’ARIA comme une API publique. Si tu ne sais pas la maintenir, ne la publie pas. Reviens au natif, et ajoute seulement ce que tu comprends vraiment.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !