aria-hidden, cacher aux lecteurs d'écran sans cacher à l'œil
💡 Sujet
Un lecteur d'écran ne lit pas la page telle qu'elle s'affiche : il lit l'arbre d'accessibilité,
une version de l'arbre DOM que le navigateur construit à l'intention des technologies d'assistance.
aria-hidden="true" est l'attribut qui retire un élément de cet arbre sans rien changer à
l'écran : le contenu reste parfaitement visible, mais il devient inexistant pour une personne
aveugle. C'est très utile pour les décorations, et très dangereux dès qu'on l'applique au mauvais
élément.
⚙️ Ce que fait exactement l'attribut
<span aria-hidden="true">★</span>L'étoile s'affiche normalement. Elle n'est simplement jamais annoncée. Deux choses importantes :
- L'effet est récursif : l'élément et tout son sous-arbre disparaissent de l'arbre
d'accessibilité, y compris les textes, les images et les
aria-labelqu'il contient. - Rien d'autre ne change : ni l'affichage, ni la mise en page, ni l'ordre de tabulation, ni le comportement de la souris.
<div aria-hidden="true">
<p>Ce paragraphe est visible mais jamais lu.</p>
<img src="chart.png" alt="Un texte alternatif qui ne servira à rien">
</div>Le alt de l'image ci-dessus est mort-né : le parent a déjà retiré tout le sous-arbre.
🆚 Ce que ce n'est pas : les quatre façons de « cacher »
C'est là que se joue la compréhension. « Caché » n'a pas le même sens selon qu'on parle de l'écran, de l'arbre d'accessibilité ou du clavier — et les techniques ne sont pas interchangeables.
| Technique | Visible à l'écran | Lu par un lecteur d'écran | Atteignable au clavier |
|---|---|---|---|
aria-hidden="true" | ✅ oui | ❌ non | ⚠️ oui (le piège) |
display: none / visibility: hidden | ❌ non | ❌ non | ❌ non |
attribut hidden | ❌ non | ❌ non | ❌ non |
classe utilitaire .sr-only | ❌ non | ✅ oui | ✅ oui |
attribut inert | ✅ oui | ❌ non | ❌ non |
Les deux lignes qui comptent sont les deux extrêmes, exactement symétriques :
<span aria-hidden="true">Vu, jamais entendu.</span>
<span class="sr-only">Entendu, jamais vu.</span>Et la classe .sr-only (aussi appelée visually-hidden), qui est le pendant d'aria-hidden, se
définit ainsi — surtout pas avec display: none, qui cacherait aussi aux lecteurs d'écran :
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}💡 Si le contenu doit disparaître pour tout le monde, display: none ou l'attribut hidden
suffisent : ajouter aria-hidden="true" par-dessus est redondant et sans effet.
✅ Les bons usages
1. Les icônes et les décorations
C'est le cas le plus fréquent, et de loin. Une icône purement décorative n'apporte rien à l'oreille, et se fait souvent annoncer d'une manière absurde (« étoile blanche », « emoji visage souriant », ou le nom du fichier SVG).
<button>
<svg aria-hidden="true" focusable="false" width="16" height="16">
<use href="#icon-trash"></use>
</svg>
Supprimer
</button>Le bouton s'annonce « Supprimer, bouton ». Sans aria-hidden, on risque « graphique Supprimer,
bouton » ou pire.
⚠️ focusable="false" en plus : sur les vieux Internet Explorer / Edge, un <svg> inline était
focusable au clavier. L'attribut est inoffensif ailleurs et reste une habitude répandue.
Même chose pour un emoji ou un caractère décoratif :
<h2><span aria-hidden="true">🚀</span> Démarrage rapide</h2>2. Le bouton avec icône seule : aria-hidden et aria-label
Le duo classique. Le contenu visuel est masqué à l'oreille, et un nom accessible est fourni à la place, sur le bouton, pas sur l'icône :
<button aria-label="Fermer la boîte de dialogue">
<svg aria-hidden="true" focusable="false"><use href="#icon-close"></use></svg>
</button>Sans aria-label, le bouton devient un « bouton » anonyme : impossible de savoir ce qu'il fait.
3. Le texte déjà dit, dit une deuxième fois
Quand la même information existe deux fois — une fois pour l'œil, une fois pour l'oreille — il faut en taire une :
<a href="/article/42">
Lire l'article
<span aria-hidden="true"> →</span>
</a>La flèche est un signal visuel ; « flèche vers la droite » à la fin de chaque lien est du bruit.
Autre exemple, une donnée présentée deux fois avec des formulations différentes :
<p>
<span aria-hidden="true">12/03/2026</span>
<span class="sr-only">12 mars 2026</span>
</p>L'œil lit la date compacte, l'oreille entend la date lisible — au lieu de « douze slash zéro trois slash deux mille vingt-six ».
⛔ La règle d'or : jamais sur un élément focusable, ni sur un de ses ancêtres
C'est l'erreur numéro un, et elle produit un bug bien identifié : l'élément focusable mais caché.
<div aria-hidden="true">
<a href="/profil">Mon profil</a>
</div>Ce lien est toujours dans l'ordre de tabulation : Tab l'atteint. Mais comme il n'existe pas dans
l'arbre d'accessibilité, le lecteur d'écran n'a rien à annoncer quand le focus arrive dessus.
L'utilisateur se retrouve avec un focus posé sur du vide, sans savoir où il est ni comment en
sortir. C'est l'un des échecs les plus déroutants qu'on puisse produire, et les outils d'audit
(axe, Lighthouse) le remontent explicitement.
Sont concernés tous les éléments naturellement focusables — <a href>, <button>, <input>,
<select>, <textarea>, <summary>, <video controls> — ainsi que tout élément portant un
tabindex="0" ou supérieur.
La bonne solution : inert
Quand on veut vraiment neutraliser une zone entière — l'arrière-plan pendant l'ouverture d'une
modale, un panneau replié qui reste dans le flux — c'est inert qu'il faut, pas aria-hidden.
Il fait les deux à la fois : retirer de l'arbre d'accessibilité et du parcours clavier.
<main inert>
<a href="/profil">Mon profil</a>
</main>
<dialog open>
<h2>Confirmer la suppression</h2>
<button>Annuler</button>
</dialog>const fond = document.querySelector("main");
function ouvrirModale() {
fond.inert = true; // plus focusable, plus lu
}
function fermerModale() {
fond.inert = false;
}💡 Avec <dialog> ouvert via showModal(), le navigateur rend déjà le reste de la page inerte tout
seul : il n'y a rien à ajouter.
Le fallback historique
Si inert n'est pas envisageable, la parade consiste à retirer les éléments du parcours clavier
en même temps que du sous-arbre :
zone.setAttribute("aria-hidden", "true");
zone.querySelectorAll("a, button, input, select, textarea, [tabindex]")
.forEach(el => el.setAttribute("tabindex", "-1"));C'est fastidieux, fragile dès que le contenu est dynamique, et ça n'empêche pas le clic à la souris.
D'où la préférence pour inert.
⚠️ Trois autres pièges
1. aria-hidden="false" ne « rallume » pas un sous-arbre
Une intuition très répandue, et fausse : on ne peut pas ré-exposer un enfant dont un ancêtre est caché.
<div aria-hidden="true">
<p aria-hidden="false">Ce texte reste invisible pour les lecteurs d'écran.</p>
</div>Le masquage d'un ancêtre est définitif pour tout le sous-arbre. En pratique, aria-hidden="false"
est traité de manière inégale par les navigateurs et n'apporte rien : pour « ne pas cacher », il
faut retirer l'attribut, pas le passer à false.
el.removeAttribute("aria-hidden"); // ✅
el.setAttribute("aria-hidden", "false"); // ⚠️ à éviter2. Ne jamais cacher du contenu qui n'existe nulle part ailleurs
aria-hidden ne « nettoie » pas une page : il en supprime un morceau pour une catégorie
d'utilisateurs. La question à se poser à chaque usage : l'information portée par cet élément
existe-t-elle ailleurs pour qui ne voit pas l'écran ?
<span aria-hidden="true">⚠️</span> Le paiement a échoué.✅ L'emoji est décoratif, le message texte porte l'information.
<span aria-hidden="true">Solde : -240 €</span>❌ L'information disparaît purement et simplement.
3. Une image décorative n'a pas besoin d'aria-hidden
Pour une <img>, un alt vide fait déjà le travail, et c'est la forme canonique :
<img src="separateur.png" alt="">
<img src="separateur.png" alt="" aria-hidden="true">La première ligne est la forme recommandée ; la seconde ajoute un attribut qui ne change rien.
⚠️ Attention à ne pas confondre alt="" (attribut présent et vide → image ignorée) avec l'absence
de alt, qui pousse certains lecteurs d'écran à annoncer le nom du fichier.
🔍 Vérifier son travail
Trois façons de contrôler, de la plus rapide à la plus fiable :
- L'arbre d'accessibilité dans les DevTools. Chrome : onglet Elements → panneau
Accessibility → Accessibility Tree. Un élément
aria-hiddeny apparaît barré ou absent, et le panneau affiche le nom accessible calculé pour l'élément sélectionné. - La navigation au clavier.
Tabde haut en bas : si le focus atterrit quelque part sans qu'aucun contour ne soit visible, ou dans une zone censée être masquée, il y a unaria-hiddenmal placé. - Un vrai lecteur d'écran, la seule vérification qui compte vraiment. VoiceOver (
Cmd + F5) sur macOS et iOS, NVDA (gratuit) sur Windows, TalkBack sur Android. Une session de cinq minutes sur sa propre page apprend plus que n'importe quelle documentation.
🧩 Résumé
aria-hidden="true"retire un élément et tout son sous-arbre de l'arbre d'accessibilité : toujours visible à l'écran, jamais annoncé.- Ce n'est ni
display: none(qui cache aux deux) ni.sr-only(qui fait exactement l'inverse : invisible mais lu). - Bons usages : icônes et emojis décoratifs, texte déjà présent sous une autre forme, et l'icône
d'un bouton dont le nom est porté par un
aria-label. - Règle d'or : jamais sur un élément focusable ni sur un de ses ancêtres — on crée sinon un focus « dans le vide », impossible à comprendre pour l'utilisateur.
- Pour neutraliser une zone entière (arrière-plan d'une modale), utiliser
inert, qui retire à la fois de l'arbre d'accessibilité et de l'ordre de tabulation. aria-hidden="false"ne ré-expose pas un enfant dont un ancêtre est caché : il faut retirer l'attribut, pas le passer àfalse.- Une information qui n'existe nulle part ailleurs ne doit jamais être masquée.
- Pour une image décorative,
alt=""suffit — et l'absence totale dealtn'est pas équivalente.