« NVDA annonce les liens du menu alors que je ne le vois même pas à l’écran » : ce retour de test revient régulièrement sur des menus mobiles construits avec une simple classe CSS pour basculer l’affichage. Le menu est bien masqué visuellement — la personne voyante ne voit rien d’anormal — mais un lecteur d’écran continue de parcourir et d’annoncer chaque lien du menu, comme si celui-ci était pleinement ouvert et visible.
Symptôme
Un menu hamburger, fermé par défaut, utilise une classe CSS du type .menu--ferme qui applique max-height: 0; overflow: hidden; ou une transformation de translation hors écran, pour permettre une animation d’ouverture fluide. Visuellement, rien ne dépasse. Mais en navigant avec un lecteur d’écran en mode exploration, chaque lien du menu est annoncé normalement, y compris lorsque le bouton hamburger affiche encore l’état « fermé ».
Diagnostic

Le masquage visuel par CSS (overflow: hidden, translation, opacité à zéro sans display: none) ne retire pas nécessairement l’élément de l’arbre d’accessibilité exposé aux technologies d’assistance. Seules certaines propriétés CSS précises (display: none, visibility: hidden) sont interprétées par les navigateurs comme un signal de retrait de l’arbre d’accessibilité. Une animation d’ouverture qui repose sur max-height ou une transformation ne déclenche pas ce retrait : le contenu reste dans le DOM, actif et lisible, alors même qu’il est invisible à l’écran.
C’est exactement ce qui se produit sur ce menu : l’animation CSS choisie pour un rendu visuel fluide laisse le menu techniquement « ouvert » pour les technologies d’assistance, tant qu’aucun attribut aria-hidden="true" n’est appliqué explicitement à l’état fermé.
Correctif
La correction consiste à synchroniser l’attribut aria-hidden avec l’état réel du menu, dans le même bloc JavaScript qui gère déjà l’ouverture et la fermeture visuelle :
const bouton = document.querySelector('.menu-toggle');
const menu = document.querySelector('.menu-mobile');
function basculerMenu(ouvert) {
menu.classList.toggle('menu--ouvert', ouvert);
menu.setAttribute('aria-hidden', ouvert ? 'false' : 'true');
bouton.setAttribute('aria-expanded', ouvert ? 'true' : 'false');
// Empêche aussi la tabulation vers les liens masqués
menu.querySelectorAll('a').forEach((lien) => {
lien.setAttribute('tabindex', ouvert ? '0' : '-1');
});
}
bouton.addEventListener('click', () => {
const estOuvert = menu.classList.contains('menu--ouvert');
basculerMenu(!estOuvert);
});
// État initial
basculerMenu(false);
Le point souvent négligé est la ligne sur tabindex : sans elle, même avec aria-hidden="true" correctement posé, un utilisateur au clavier peut encore tabuler jusqu’à un lien masqué visuellement, ce qui crée une confusion différente mais tout aussi problématique.
Vérification
Le test de non-régression consiste à ouvrir les outils de développement, section accessibilité, et vérifier que l’arbre d’accessibilité du menu passe bien de « caché » à « visible » lors du clic sur le bouton hamburger. Un second test à la tabulation seule, menu fermé, doit confirmer qu’aucun lien du menu n’est atteint avant le prochain élément visible de la page.
Prévention
- Ne jamais considérer qu’un masquage visuel équivaut automatiquement à un masquage pour les technologies d’assistance.
- Centraliser la logique d’ouverture/fermeture dans une seule fonction qui met à jour classe CSS,
aria-hidden,aria-expandedettabindexensemble, jamais séparément. - Ajouter ce cas précis à la checklist de recette de tout nouveau composant de menu, avant même de tester le rendu visuel sur mobile.
Un menu invisible à l’œil n’est pas forcément invisible pour un lecteur d’écran : seule la synchronisation explicite des attributs ARIA avec l’état visuel garantit la cohérence entre les deux mondes.
En résumé
L’oubli d’aria-hidden sur un menu mobile fermé est l’un des bugs d’accessibilité les plus silencieux, car il ne se voit jamais sur un écran classique. Centraliser la bascule d’état dans une seule fonction qui synchronise CSS, ARIA et tabulation évite ce piège de façon durable, sur ce menu comme sur tout futur composant construit sur le même modèle.