Le menu de navigation
Un menu de navigation, ce sont trois pièces : un nav qui porte un nom, une vraie liste de liens, une marque sur la page affichée. Et le bouton qui l'ouvre est un bouton, pas une case à cocher détournée. Démo, code, critères RGAA.
6 critères RGAA en jeu
Essayez-le
Tabulez : le focus passe par le bouton, puis par chaque lien du menu ouvert.
Refermez le menu, tabulez encore — plus aucun arrêt, parce que le menu est
masqué et non seulement replié. Puis relevez les repères : les deux nav portent un nom distinct, et un seul lien est marqué comme la page affichée.
Cette démo existe aussi en page autonome.
L’erreur courante
Deux nav sans nom, des liens qui ne forment pas une liste, et un état
d’ouverture qui n’existe que dans une classe CSS.
<!-- À ne pas faire -->
<div class="burger" onclick="basculer()">☰</div>
<nav class="menu">
<a href="/">Accueil</a>
<a href="/produits">Produits</a>
<a href="/contact" class="actif">Contact</a>
</nav>
…
<nav>
<a href="/rubrique/tarifs">Tarifs</a>
<a href="/rubrique/options">Options</a>
</nav> /* À ne pas faire : le CSS sait que le menu est ouvert, personne d'autre. */
.menu {
left: -9999px;
}
.menu.ouvert {
left: 0;
} Ce que cela coûte, dans l’ordre où on s’en aperçoit.
Un lecteur d’écran dresse la liste des repères de la page pour y sauter directement. Ici, il annonce « navigation, navigation » : deux entrées identiques, rien pour choisir. C’est un raccourci de navigation rendu inutile par un attribut manquant.
Les liens ne sont pas dans une liste : ni le nombre d’entrées, ni le rang de celle qu’on lit. On avance sans savoir s’il en reste deux ou douze.
Le div cliquable ne prend pas le focus, ne répond ni à Entrée ni à
Trois variantes, aussi répandues :
- La case à cocher détournée — un
<input type="checkbox">masqué, un<label>en guise de bouton, et:checked ~ .menupour ouvrir. Le clavier fonctionne, mais l’annonce est fausse : « case à cocher, non cochée » là où il faudrait « bouton, réduit ». L’utilisateur cherche un formulaire. role="menu"sur une navigation de site — ces rôles décrivent le menu d’une application, avec navigation aux flèches et un seul arrêt de tabulation. Posés sur des liens ordinaires, ils promettent un comportement que le composant ne tient pas.- La page courante distinguée par la seule couleur — invisible pour qui ne la perçoit pas, inaudible pour qui écoute.
Le code, pas à pas
1. Un <nav>, et un nom dès qu’il y en a deux
<nav aria-label="Principale">…</nav>
<nav aria-label="Dans cette rubrique">…</nav> <nav> est un repère : un lecteur d’écran en dresse la liste et permet d’y
sauter sans traverser la page. Un seul nav n’a pas besoin de nom — « navigation »
le désigne sans ambiguïté. À partir de deux, le nom est la seule chose qui les
distingue.
Le nom ne répète pas le rôle. Écrivez aria-label="Principale", pas aria-label="Navigation principale" : le rôle est déjà annoncé, et
« navigation Navigation principale » n’ajoute rien.
Le critère 9.2 va dans les deux sens : les zones de navigation se structurent
par un nav, et le nav leur est réservé. Une grille de cartes, une liste
de partenaires, une barre d’actions ne sont pas des zones de navigation ; leur
poser un nav ajoute un repère qui égare au lieu d’aider.
2. Une vraie liste de liens
<nav aria-label="Principale">
<ul>
<li><a href="/">Accueil</a></li>
<li><a href="/produits">Produits</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav> Le glossaire du RGAA définit le menu de navigation comme une « liste de liens » : ce n’est pas une préférence de mise en forme. Ce que la liste apporte tient en deux mots — le nombre et le rang : « liste de 3 éléments, Produits, lien, 2 sur 3 ». Sans elle, des liens qui se suivent, et rien pour savoir où l’on en est.
La disposition horizontale ne change rien : c’est du CSS posé sur une liste, pas autre chose qu’une liste.
Si les puces sont retirées (list-style: none), ajoutez role="list" — Safari
retire alors le rôle de liste, et VoiceOver n’annonce plus rien. Ce n’est pas
une non-conformité au critère 9.3, c’est une précaution ; la fiche « listes » détaille pourquoi.
3. aria-current sur la page affichée
<li><a href="/produits" aria-current="page">Produits</a></li> aria-current="page" désigne le lien qui mène à la page où l’on se trouve, et
lui seul : un par page. Aucun test du RGAA ne l’exige nommément, mais le
glossaire du critère 3.1 le range parmi les compléments au niveau du code
attendus dès que la page affichée est distinguée à l’œil — c’est l’objet du
paragraphe suivant.
Deux précautions valent d’être prises ensemble.
[aria-current='page'] {
font-weight: 700;
box-shadow: inset 3px 0 0 0 var(--accent);
} Le style suit l’attribut, pas une classe : l’état vu et l’état exposé viennent
alors de la même source et ne peuvent pas diverger. Le défaut classique est une
classe .actif tenue à jour et un aria-current oublié en chemin.
Et la distinction visuelle ne repose pas sur la seule couleur. Le glossaire « Information (donnée par la couleur) »,
rattaché au critère 3.1, demande les deux ensemble : « Un
autre style typographique (gras, italique, taille de texte, autre police, etc)
et par le biais d’un complément au niveau du code (aria-label, title, texte
masqué, aria-current, etc.) ». La graisse et le trait pour l’œil, aria-current="page" pour les technologies d’assistance.
4. Le bouton d’ouverture, et son état
<button type="button" aria-expanded="false" aria-controls="menu-principal">
Menu
</button>
<nav id="menu-principal" aria-label="Principale" hidden>…</nav> Le <button> donne le focus, Entrée, Espace et l’annonce
« bouton » — quatre comportements qu’on ne réécrit pas, et que ni un div ni un <label> de case à cocher ne rendent.
aria-expanded porte l’état, et c’est son seul domicile. Les trois barres qui
se changent en croix disent la même chose à l’œil : doublon utile, à condition
d’être masquées par aria-hidden="true" pour ne pas être annoncées en plus.
Le nom du bouton, lui, ne bouge pas : « Menu », ouvert comme fermé. Un intitulé
qui bascule entre « Ouvrir le menu » et « Fermer le menu » se défend, mais il ne
dispense pas d’aria-expanded — l’intention et l’état sont deux informations
différentes.
Enfin, si le menu est déployé en permanence au-delà d’un point de rupture, le
bouton doit alors disparaître vraiment, par display: none. Réduit à zéro
pixel ou rendu transparent, il reste focalisable, et la tabulation s’arrête sur
un bouton que personne ne voit.
5. Le menu refermé : masqué, pas déplacé
<nav id="menu-principal" aria-label="Principale" hidden>…</nav> hidden retire le menu de l’affichage et de l’ordre de tabulation, en un
attribut. Un déplacement hors écran, une hauteur nulle ou une opacité à zéro ne
font que la moitié du travail : le contenu reste restituable et focalisable,
alors qu’il a vocation à être ignoré tant qu’il est fermé.
Attention à une règle CSS qui poserait un display sur l’élément masqué : elle
l’emporte sur le display: none que le navigateur applique à [hidden], et le
menu reste là sans que rien ne le signale. Un nav mis en display: flex suffit à annuler l’attribut.
6. Les sous-menus déroulants — en avez-vous besoin ?
Un sous-menu qui ne s’ouvre qu’au survol de la souris n’existe pas au clavier : c’est le défaut le plus courant des menus à niveaux, et le critère 12.11 le vise directement. Ce qui s’ouvre au survol doit s’ouvrir aussi à la prise de focus ou à l’activation, et rester ouvert le temps qu’on le parcourt.
Le motif sûr est celui du point 4, répété : chaque entrée à sous-menu est un <button aria-expanded> suivi de sa propre liste. C’est le Disclosure
Navigation Menu de l’ARIA APG, et il n’emploie aucun role="menu".
Mais posez d’abord la question : un menu principal court, doublé d’un sommaire de rubrique sur chaque page, remplace la plupart des menus déroulants. Il ne demande aucun script, se parcourt sans viser à la souris, et s’imprime.
Au clavier
| Touche | Effet |
|---|---|
| Tab | Amène au bouton, puis à chaque lien du menu ouvert. Le menu refermé n’ajoute aucun arrêt. |
| Entrée / Espace | Sur le bouton : ouvre ou referme le menu. Le focus reste sur le bouton. |
| Entrée | Sur un lien : suit le lien. |
| Échap | Referme le menu et ramène le focus au bouton qui l’a ouvert. |
| Navigation par repères | Saute d’un nav à l’autre — c’est leur nom qui permet de choisir. |
Les flèches ne servent pas ici. Elles appartiennent à role="menubar", c’est-à-dire
au menu d’une application, pas à une navigation de site faite de liens.
Ce que le lecteur d’écran restitue
Sur le bouton : « Menu, bouton, réduit », puis « développé » après activation. En entrant dans le menu : « Principale, navigation, liste de 4 éléments, Accueil, lien, 1 sur 4 ». Sur le lien de la page affichée : « Produits, lien, page courante, 2 sur 4 ».
La formulation varie d’un lecteur à l’autre ; les informations, non — le nom du repère, le nombre d’entrées, le rang, la page courante.
Le diagnostic inverse est plus utile encore. Si la liste des repères annonce
deux fois « navigation » sans les distinguer, un aria-label manque. Si vous
n’entendez pas « 1 sur 4 », les liens ne sont pas dans une liste — ou la liste a
perdu son rôle en perdant ses puces. Si le bouton reste « réduit » une fois le
menu ouvert, aria-expanded n’est pas mis à jour et seule la classe CSS bouge.
Checklist
- Un
<nav>par zone de navigation, et rien d’autre dans un<nav>. - Un
aria-labelsur chacun dès qu’il y en a deux, sans y répéter « navigation ». - Les liens dans une
<ul>, avecrole="list"si les puces sont retirées. aria-current="page"sur un seul lien, jamais distingué par la seule couleur.- Un
<button>avecaria-expandedtenu à jour — jamais une case à cocher. - Le menu refermé masqué par
hidden, pas déplacé hors de l’écran. - Les sous-menus atteignables au clavier, pas seulement au survol.
Les critères RGAA en jeu
- Critère 9.2 — Cohérence de la structure du document
Une zone de navigation se structure par une balise nav, et la balise nav lui est réservée : un menu bricolé en div ne devient pas un repère, et un nav posé sur une grille de cartes en annonce un qui n'existe pas.
- Critère 9.3 — Structure des listes
Le glossaire définit le menu comme une liste de liens. Sans ul et li, plus de nombre ni de rang : on entend des liens sans savoir combien il en reste, et retirer les puces peut suffire à perdre ce rôle.
- Critère 7.1 — Compatibilité des scripts
Le bouton qui déploie le menu est un composant d'interface piloté par script : son nom, son rôle et son changement d'état doivent être exposés, ce qu'aria-expanded fait et qu'une classe CSS ne fait pas.
- Critère 12.8 — Cohérence de l’ordre de tabulation
Un menu refermé par un déplacement hors écran ou une hauteur nulle garde ses liens dans l'ordre de tabulation : le focus quitte l'écran, traverse des liens invisibles, puis réapparaît plus bas. Le masquer pour de bon — hidden — est ce qui l'en retire.
- Critère 12.11 — Contenus additionnels au clavier
Un sous-menu qui ne s'ouvre qu'au survol de la souris n'existe pas au clavier, alors qu'il contient souvent la moitié des rubriques du site.
- Critère 12.2 — Place constante du menu
Le menu doit garder la même place et le même rang dans le code source d'une page à l'autre : un gabarit intérieur qui injecte sa barre après le contenu détruit le repère que l'utilisateur s'est construit. La page d'accueil est exclue du critère.
Vérifiez votre composant avec l'extension
Pour aller plus loin
- Les listes : pourquoi un menu est une liste, et ce que
role="list"répare quand les puces disparaissent. - L’accordéon : le même couple bouton +
aria-expanded, appliqué à du contenu plutôt qu’à des liens. - Critère 12.1 — Deux systèmes de navigation : le menu n’en est qu’un ; il faut un plan du site ou un moteur de recherche à côté.
- « Menu et barre de navigation » au glossaire : la définition qui décide de ce qui est un menu, et de ce qui n’en est pas un.
- « Landmarks » au glossaire : les repères de page, et le rôle attendu de chacun.
Sources
- ARIA APG — Disclosure Navigation Menu — le motif du menu déroulant, sans
role="menu" - MDN —
<nav>— l’élément, et ce qui n’en relève pas - WCAG — Understanding 3.2.3 Consistent Navigation — pourquoi le menu ne doit pas bouger d’une page à l’autre
- W3C WAI — Menus tutorial — structure, styles et comportements attendus
- GOV.UK Design System — Header — une barre de navigation de référence, bouton d’ouverture compris