ARIA, ce scalpel de l'accessibilité web
Table des matières
Un outil de précision
Le rapport WebAIM Million 2026 est sans appel : les pages qui utilisent ARIA ont plus d'erreurs d'accessibilité que celles qui n'en utilisent pas. J'explique pourquoi dans ARIA, ce poison de l'accessibilité web, avec les quatre règles que le W3C a posées pour limiter les dégâts.
Mais ARIA n'est pas un poison par nature. C'est un scalpel : indispensable entre les bonnes mains, dangereux entre les mauvaises. Le HTML a des limites réelles, et quand tu les atteins, ARIA est la seule réponse.
Cet article va te montrer dans quels cas ARIA est le bon outil, quels anti-patterns éviter à tout prix, et comment tester en profondeur avec un lecteur d'écran.
Quand ARIA est la bonne réponse
Si la première règle du W3C est "n'utilise pas ARIA", c'est qu'ARIA existe pour les cas où le HTML ne suffit pas. Et ces cas existent. Pas sur chaque page, mais quand tu les rencontres, ARIA est la seule réponse.
Le contenu qui change sans prévenir
Un message de succès qui apparaît après l'envoi d'un formulaire. Un compteur de panier qui se met à jour. Une notification d'erreur de connexion. Un message dans un chat. Dans tous ces cas, le contenu change visuellement, mais le lecteur d'écran ne le sait pas. Il lit la page telle qu'elle était au chargement. Sans instruction contraire, le nouveau contenu est invisible.
C'est le problème que résolvent les live regions, le seul mécanisme qui permet de dire au lecteur d'écran "quelque chose a changé, annonce-le". Sara Soueidan en détaille chaque nuance dans sa série de référence sur le sujet.
<!-- Confirmation non urgente — le lecteur d'écran
attend une pause dans sa lecture -->
<p aria-live="polite">
Article ajouté au panier (3 articles).
</p>
<!-- Erreur critique — le lecteur d'écran
interrompt immédiatement sa lecture -->
<p aria-live="assertive">
Connexion perdue. Tes modifications ne sont pas
enregistrées.
</p>aria-live="polite" est une notification discrète : le lecteur d'écran attend que
l'utilisateur soit disponible avant d'annoncer le changement. aria-live="assertive" est
une interruption : elle coupe ce que le lecteur d'écran est en train de dire pour annoncer
le message immédiatement. La règle est simple : si l'utilisateur peut attendre, c'est
polite. Si l'information est critique et urgente, c'est
assertive.
L'élément <output> porte un rôle implicite status avec une
live region polite,
mais il est conçu pour les résultats de calcul, pas pour les notifications génériques. Pour
le cas général, des confirmations d'action aux alertes d'erreur, aucun élément HTML natif ne
couvre le besoin. C'est un cas où ARIA est la seule réponse.
Les widgets que le HTML n'a pas
Le HTML a des boutons, des liens, des cases à cocher, des champs de texte, des listes déroulantes. Mais il n'a pas d'onglets, pas d'arborescence dépliable, pas de combobox riche avec filtrage et groupes.
Le GOV.UK Design System et
Heydon Pickering dans Inclusive Components
montrent comment construire des onglets accessibles à partir d'une liste de liens internes :
une <ul> avec des <li> et des <a href="#section">. Sans JavaScript, c'est une table
des matières fonctionnelle. Avec JavaScript, la liste devient un groupe d'onglets :
<!-- Sans JavaScript : une table des matières -->
<ul>
<li><a href="#general">Général</a></li>
<li><a href="#securite">Sécurité</a></li>
<li><a href="#notifications">Notifications</a></li>
</ul>
<!-- Avec JavaScript : les rôles ARIA
transforment la sémantique -->
<ul role="tablist">
<li role="presentation">
<a role="tab" href="#general" id="tab-general"
aria-controls="general" aria-selected="true">Général</a>
</li>
<li role="presentation">
<a role="tab" href="#securite" id="tab-securite"
aria-controls="securite" aria-selected="false"
tabindex="-1">Sécurité</a>
</li>
<li role="presentation">
<a role="tab" href="#notifications" id="tab-notifications"
aria-controls="notifications" aria-selected="false"
tabindex="-1">Notifications</a>
</li>
</ul>
<section id="general" role="tabpanel" aria-labelledby="tab-general">
<!-- ... -->
</section>La <ul> devient un tablist, les <a> deviennent des
tab, jusque-là, c'est la sémantique qui évolue avec le comportement.
Mais les <li> posent un problème : dans un tablist, il n'y a pas
de "liste d'éléments". Si tu les laisses tels quels, le lecteur d'écran annonce "liste,
3 éléments, onglet, Général", un mélange de deux sémantiques qui n'a aucun sens. Le
role="presentation" efface la sémantique de list item sans retirer
l'élément du DOM, c'est exactement le
cas prévu par la deuxième règle d'ARIA : modifier la sémantique
native quand elle entre en conflit avec le rôle réel de l'élément.
Le balisage ne fait que la moitié du chemin. Chaque <a> pointe vers son panneau via
aria-controls, chaque panneau reçoit role="tabpanel"
et aria-labelledby, et le JavaScript doit encore fournir la couche
clavier : flèches gauche/droite pour circuler entre les onglets (le
tabindex="-1" sort les onglets inactifs du parcours Tab, c'est le
roving tabindex du pattern de
l'APG), bascule
d'aria-selected, masquage des panneaux inactifs. Sans cette couche,
le composant annonce "onglet" mais se comporte comme une liste de liens : la promesse du
rôle n'est pas tenue.
Le même principe s'applique aux tree views (une arborescence
navigable au clavier) ou aux combobox riches (un champ avec filtrage,
groupes et navigation clavier que <datalist> ne couvre pas). Dans tous ces cas,
ARIA ne remplace pas le HTML, il le complète.
Les états que le HTML ne peut pas exprimer
Un menu déroulant est ouvert ou fermé. Un lien pointe vers la page courante. Un champ est lié à un message d'aide. Le HTML ne sait pas exprimer ces états, ARIA le fait :
<!-- Le bouton annonce "Menu, réduit" ou
"Menu, développé" -->
<button aria-expanded="false"
aria-controls="menu-navigation">
Menu
</button>
<ul id="menu-navigation" hidden>
<li><a href="/produits">Produits</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
<!-- Le lecteur d'écran annonce
"Accueil, lien, page actuelle" -->
<nav aria-label="Navigation principale">
<a href="/" aria-current="page">Accueil</a>
<a href="/produits">Produits</a>
<a href="/contact">Contact</a>
</nav>
<!-- Le message d'aide est lu après le champ -->
<label for="email">Adresse email</label>
<input id="email" type="email"
aria-describedby="email-aide">
<p id="email-aide">
Utilisée uniquement pour la confirmation
de commande.
</p>aria-expanded transforme un bouton muet en bouton qui annonce son
état. aria-current="page" dit à l'utilisateur où il se trouve dans
la navigation, sans ça, tous les liens se ressemblent.
aria-describedby associe un texte d'aide à un champ sans toucher à
son label : le lecteur d'écran lit d'abord le label, puis la
description complémentaire.
Aucun attribut HTML natif ne fait ce travail. C'est ARIA dans son rôle exact : exprimer ce que le HTML ne peut pas dire.
Les anti-patterns qui empoisonnent
Tu sais maintenant quand ARIA est la bonne réponse. Léonie Watson navigue le web au lecteur d'écran huit heures par jour, et ce qu'elle entend le plus souvent, ce sont les mêmes erreurs qui reviennent. Voici les plus répandues.
Le nom qui contredit l'étiquette
<!-- ❌ Le bouton affiche "Rechercher" mais
le lecteur d'écran annonce "Trouver un produit" -->
<button aria-label="Trouver un produit">
Rechercher
</button>L'utilisateur qui navigue à la voix voit le mot "Rechercher" et dit "clic Rechercher".
Rien ne se passe, parce que le nom accessible du bouton est "Trouver un produit", pas
"Rechercher". Le aria-label a remplacé le texte visible dans
l'arbre d'accessibilité.
Le RGAA
l'interdit à travers trois critères :
11.2
pour les champs de formulaire,
11.9
pour les boutons,
6.1
pour les liens : l'intitulé visible doit être contenu dans le nom accessible. Si tu
veux ajouter du contexte, utilise aria-describedby, il complète le
nom sans le remplacer :
<!-- ✅ Le texte visible est le nom accessible,
le contexte supplémentaire est en description -->
<button aria-describedby="search-help">
Rechercher
</button>
<p id="search-help" hidden>
Dans le catalogue produits
</p>Le contenu que tu fais disparaître
<!-- ❌ Le produit épuisé est grisé visuellement,
le dev le masque aussi pour le lecteur
d'écran — mais le lien reste focusable -->
<article class="product sold-out"
aria-hidden="true">
<h3>Casque audio sans fil</h3>
<p>89,99 € — <strong>Épuisé</strong></p>
<a href="/product/wireless-headphones">Voir le produit</a>
</article>Le développeur a grisé le produit épuisé et pensait logiquement le masquer aussi pour le
lecteur d'écran. Mais l'information reste utile : le prix, la disponibilité, le lien vers
la fiche produit. Et surtout, aria-hidden="true" est hérité par
tous les descendants : le lien disparaît de l'arbre d'accessibilité mais reste focusable
au clavier. L'utilisateur arrive sur un élément muet, pas de nom, pas de destination.
Visuellement atténué ne veut pas dire inutile. Si le contenu doit rester dans la page, il doit rester dans l'arbre d'accessibilité :
<!-- ✅ Le produit reste accessible,
son état est annoncé clairement -->
<article class="product sold-out">
<h3>Casque audio sans fil</h3>
<p>89,99 € — <strong>Épuisé</strong></p>
<a href="/product/wireless-headphones">Voir le produit</a>
</article>
<!-- ✅ Si le contenu doit vraiment disparaître,
hidden le retire du rendu ET de l'arbre
d'accessibilité -->
<article class="product sold-out" hidden>
<h3>Casque audio sans fil</h3>
<p>89,99 € — <strong>Épuisé</strong></p>
<a href="/product/wireless-headphones">Voir le produit</a>
</article>Les rôles qui ne servent à rien
<!-- ❌ Redondant — ces éléments HTML portent
déjà ces rôles implicitement -->
<nav role="navigation">...</nav>
<main role="main">...</main>
<header role="banner">...</header>
<footer role="contentinfo">...</footer>
<button role="button">Envoyer</button>
<a role="link" href="/contact">Contact</a>Un <nav> est un navigation. Un <main> est un
main. Un <button> est un button.
Ajouter le rôle explicite sur un élément qui le porte déjà, c'est écrire
color: red; color: red; en CSS :
ça ne casse rien, mais ça signale que le développeur ne sait pas ce que l'élément fait
nativement. Et ce bruit masque les vrais problèmes dans un audit.
Le aria-label que personne n'entend
<!-- ❌ aria-label sur un paragraphe —
ignoré par la plupart des lecteurs d'écran -->
<p aria-label="Avertissement important">
Ce produit contient des allergènes.
</p>Le aria-label n'est pas un attribut universel.
Ben Myers l'a
documenté en détail : la
spécification ARIA ne
l'autorise que sur les éléments dont le rôle admet un nom (widgets,
landmarks, images), pas sur les éléments statiques comme <p>,
<span> ou <div> sans rôle. Un rôle explicite ne suffit d'ailleurs pas toujours :
certains rôles comme presentation ou generic
interdisent aussi le nommage ("Name from: prohibited" dans la spec).
En pratique, la plupart des lecteurs d'écran l'ignorent sur ces éléments. Le texte du paragraphe est lu normalement, le
aria-label est perdu.
Si le texte visible ne suffit pas, la solution n'est pas un
aria-label invisible, c'est un meilleur texte visible.
J'en montre un exemple concret dans
Autopsie d'une card produit, où chaque
aria-label est pesé au cas par cas :
<!-- ✅ Le texte dit ce qu'il faut dire,
pour tout le monde -->
<p><strong>Avertissement :</strong>
ce produit contient des allergènes.</p>Le bouton sans nom
<!-- ❌ Pas de nom accessible — le lecteur d'écran
annonce juste "bouton" -->
<button>
<svg viewBox="0 0 24 24"><!-- icon --></svg>
</button>
<!-- ✅ aria-label quand l'icône seule
suffit visuellement -->
<button aria-label="Fermer la modale">
<svg viewBox="0 0 24 24" aria-hidden="true">
<!-- icon -->
</svg>
</button>
<!-- ✅✅ Encore mieux : du texte visible
à côté de l'icône -->
<button>
<svg viewBox="0 0 24 24" aria-hidden="true">
<!-- icon -->
</svg>
Fermer
</button>Un bouton avec juste une icône SVG, sans texte ni
aria-label, est annoncé "bouton", sans aucune indication de ce
qu'il fait. Le aria-label corrige le problème pour les lecteurs
d'écran, mais pas pour les personnes qui naviguent à la voix : elles voient une icône et ne
savent pas quel mot prononcer pour activer le bouton. Le texte visible est la solution la
plus robuste : il sert à la fois de nom accessible, de repère pour la navigation vocale, et
de secours quand le CSS ne charge pas.
Tester en profondeur
Les trois vérifications en 30 secondes de l'article précédent utilisent l'arbre d'accessibilité des outils de développement. C'est un bon premier filtre. Mais pour tester ce que l'utilisateur entend réellement, il faut activer un lecteur d'écran.
VoiceOver sur macOS
Active VoiceOver avec Cmd + F5 (Fn + Cmd + F5 sur les claviers récents, ou triple pression sur Touch ID). Navigue ta page avec Tab (éléments interactifs) et Ctrl + Option + flèches (lecture séquentielle). Écoute ce que le lecteur d'écran annonce pour chaque élément : le rôle, le nom, l'état.
Le rotor (Ctrl + Option + U) est ton outil d'audit structurel. Il affiche la liste des titres, des liens, des landmarks et des contrôles de formulaire. Si un élément manque dans le rotor alors qu'il devrait y être, ou s'il y apparaît avec un nom incompréhensible, ton ARIA a un problème.
Les autres lecteurs d'écran
VoiceOver n'est pas le seul outil, et tes utilisateurs n'utilisent pas tous macOS :
- NVDA + Firefox sur Windows : gratuit et open source, le plus utilisé par les développeurs sous Windows. Navigation par Tab (éléments interactifs) et flèches (lecture séquentielle). La liste d'éléments (Insert + F7) joue le même rôle que le rotor.
- JAWS + Chrome ou Edge sur Windows : la référence professionnelle, payant. Même logique de navigation, avec la liste de liens (Insert + F7) et la liste de titres (Insert + F6).
- TalkBack + Chrome sur Android : le lecteur d'écran mobile. Navigation par balayage (gauche/droite pour le séquentiel, haut/bas pour changer de mode de navigation). Le menu de lecture (balayage vers le bas puis vers la droite) donne accès aux titres, liens et contrôles.
Chaque lecteur d'écran a ses particularités, mais les signaux d'alerte restent les mêmes.
Ce qu'il faut écouter
Quatre signaux d'alerte :
-
Un rôle annoncé sans comportement. Le lecteur d'écran dit "bouton" mais Entrée ne fait rien. Tu as un
role="button"sans gestionnaire clavier. -
Un nom qui ne correspond pas. Le lecteur d'écran annonce "Trouver un produit" mais tu lis "Rechercher" à l'écran. Tu as un
aria-labelqui contredit le texte visible. -
Un silence là où tu attends du contenu. Tu navigues avec Tab et le focus s'arrête sur un élément muet. Tu as un
aria-hidden="true"sur un élément focusable. -
Une annonce qui ne vient pas. Tu soumets un formulaire, un message de succès apparaît, mais le lecteur d'écran ne dit rien. Il manque un
aria-livesur la zone de notification.
Chacun de ces signaux correspond à un anti-pattern de la section précédente. Le lecteur d'écran ne fait que révéler ce que l'arbre d'accessibilité contenait déjà.
Les critères d'ensemble
Chaque cas que tu viens de voir, les live regions, les onglets, les états, les anti-patterns, le RGAA les traduit en critères vérifiables :
- RGAA 6.1 : chaque lien est explicite
- RGAA 7.1 : chaque script est compatible avec les technologies d'assistance (ses tests couvrent la conformité des rôles, états et propriétés ARIA)
- RGAA 7.3 : chaque script est contrôlable au clavier et par tout dispositif de pointage
- RGAA 11.2 : chaque étiquette de formulaire est pertinente
- RGAA 11.9 : chaque intitulé de bouton est pertinent
Tous ces critères convergent vers la même idée : chaque rôle posé est un engagement, le composant doit se comporter comme ce rôle le promet. Chaque nom accessible est un contrat, il doit correspondre à ce que l'utilisateur voit et peut prononcer. L'arbre d'accessibilité et le lecteur d'écran te disent si tu le tiens.
Ouvre ta page, lance VoiceOver ou NVDA, et écoute. Trente secondes suffisent pour entendre ce que le lecteur d'écran annonce.
Tu as le scalpel. Il te reste à l'utiliser.
Sources
- WebAIM Million 2026 — Accessibility analysis of the top 1,000,000 home pages
- W3C — Using ARIA
- W3C — ARIA Authoring Practices Guide, Read Me First
- W3C — WAI-ARIA 1.2 Specification
- GOV.UK Design System — Tabs component
- Heydon Pickering — Tabbed Interfaces, Inclusive Components
- Léonie Watson — Tink
- TetraLogical
- Ben Myers — Don't use aria-label on static text elements
- Sara Soueidan — Accessible notifications with ARIA Live Regions
- MDN — ARIA live regions
- W3C — F96: Failure due to the accessible name not containing the visible label text
- RGAA — Critères et tests