Menubar accessible : role menubar et quand l'éviter
role menubar modélise la barre de menus d'une application (Fichier, Édition), pas la navigation d'un site : alternatives correctes, clavier à deux dimensions, ARIA et critères RGAA.
Avant toute chose : le motif menubar est presque toujours le mauvais choix sur un site web. role="menubar" modélise la barre de menus d'une application de bureau (Fichier, Édition, Affichage), pas « le menu en haut du site ». La navigation d'un site, même avec des sous-menus déroulants, se construit avec <nav> + <ul> + <a> et des disclosures, sans le moindre ARIA de menu. La barre de menus ARIA se réserve aux véritables applications web : éditeur, IDE, outil de design. C'est la même erreur que celle du menu button, un cran au-dessus. Pour un méga-menu de navigation de site, le pas-à-pas est du côté du blog : Méga-menus accessibles : Guide d'implémentation complet.
Quand ce motif est le mauvais choix
- Navigation de site, même avec des menus déroulants :
<nav>+<ul>+<a>, sans ARIA de menu. - Un « hamburger » qui révèle la navigation : un disclosure enveloppant un
<nav>. - Une rangée de listes déroulantes de liens (le méga-menu classique) :
<nav>+ un disclosure par groupe. - Une véritable barre de menus d'application (actions Fichier, Édition, Affichage dans un éditeur web) : menubar, cette fiche.
Ce qui se passe quand une navigation de site reçoit role="menubar" : le lecteur d'écran annonce « barre de menus » et bascule en mode application ; les touches de lecture habituelles cessent de fonctionner ; l'utilisateur est censé naviguer aux flèches, mais des <a href> n'implémentent pas les flèches, donc rien ne se passe. Il est bloqué dans un mode qu'il n'a pas demandé, sur un composant qui ne tient pas son propre contrat. C'est strictement pire que le même balisage sans aucun ARIA, et aucun outil ne le signalera. Si vous construisez un site, arrêtez-vous ici et utilisez <nav> : la suite de cette fiche concerne le cas rare où vous construisez réellement une barre de menus d'application.
Critères RGAA applicables
- Critère 7.1 : compatibilité des scripts avec les technologies d'assistance. Les rôles
menubar,menuetmenuitem, ainsi qu'aria-haspopupetaria-expandedsur les éléments ouvrant un sous-menu, doivent être exposés. - Critère 7.3 : script contrôlable au clavier et par tout dispositif de pointage. Navigation complète à deux dimensions : gauche et droite le long de la barre, bas dans les sous-menus, Échap pour remonter. Un sous-menu qui ne s'ouvre qu'au survol est inutilisable au clavier.
- Critère 10.7 : prise de focus visible. Le focus réel se déplace dans la barre et les sous-menus et doit rester visible à chaque étape.
- Critère 10.8 : contenus cachés ignorés des technologies d'assistance. Les sous-menus fermés se masquent avec
hidden, pas seulement en les rendant invisibles à l'écran. - Critère 12.8 : ordre de tabulation cohérent. La barre entière constitue un seul arrêt de tabulation (roving tabindex) et le focus revient à l'élément parent à la fermeture d'un sous-menu.
- Critère 12.9 : absence de piège au clavier. Échap et Tab doivent toujours permettre de sortir de la barre.
- Critère 3.1 : information donnée par la couleur. L'état coché d'un
menuitemcheckboxdoit rester perceptible autrement que par la seule couleur, par exemple par une coche générée depuis l'attribut.
Interaction clavier attendue
| Touche | Action |
|---|---|
| Flèche droite / Flèche gauche (dans la barre) | Élément suivant / précédent de la barre |
| Flèche bas (dans la barre) | Ouvre le sous-menu et place le focus sur son premier élément |
| Flèche bas / Flèche haut (dans un sous-menu) | Élément suivant / précédent |
| Flèche droite / Flèche gauche (dans un sous-menu) | Passe au sous-menu adjacent de la barre |
| Entrée / Espace | Ouvre le sous-menu ou active l'élément |
| Début / Fin | Premier / dernier élément |
| Échap | Ferme le sous-menu et rend le focus à l'élément parent de la barre |
| Tab | Quitte entièrement la barre de menus, jamais intercepté |
Rôles et attributs ARIA
Le focus réel se déplace (.focus()), jamais aria-activedescendant. Un seul élément de la barre porte tabindex="0", tous les autres tabindex="-1" (roving tabindex). Les enfants valides de role="menu" sont menuitem, menuitemcheckbox, menuitemradio et separator ; un élément qui ouvre un sous-menu porte aria-haspopup="menu" et aria-expanded. Un menuitemcheckbox porte son état via aria-checked (jamais aria-pressed : deux mécanismes feraient deux annonces).
<!-- Une authentique barre de menus d'application : des ACTIONS, pas des destinations. -->
<div role="menubar" aria-label="Éditeur">
<!-- Roving tabindex : exactement un tabindex="0" dans la barre. -->
<button type="button" role="menuitem" tabindex="0"
aria-haspopup="menu" aria-expanded="false" aria-controls="menu-fichier">
Fichier
</button>
<ul role="menu" id="menu-fichier" aria-label="Fichier" hidden>
<li role="none"><button type="button" role="menuitem" tabindex="-1">Nouveau</button></li>
<li role="none"><button type="button" role="menuitem" tabindex="-1">Ouvrir…</button></li>
<li role="none"><span role="separator"></span></li>
<!-- Option à bascule : menuitemcheckbox + aria-checked, pas aria-pressed. -->
<li role="none">
<button type="button" role="menuitemcheckbox" tabindex="-1" aria-checked="true">
Afficher la barre latérale
</button>
</li>
</ul>
</div>
/* État coché : pas par la couleur seule (critère 3.1). La coche est un contenu
généré depuis l'attribut : le visuel et l'état annoncé ne peuvent pas diverger. */
[role="menuitemcheckbox"][aria-checked="true"]::before { content: "✓ "; }
[role="menuitemcheckbox"][aria-checked="false"]::before { content: ""; }
/* Le focus réel se déplace ici : :focus-visible fonctionne. */
[role="menuitem"]:focus-visible,
[role="menuitemcheckbox"]:focus-visible {
outline: 2px solid #0056b3;
outline-offset: -2px;
}
Deux règles de script structurent tout le reste : à la fermeture d'un sous-menu par Échap, restaurer le focus sur l'élément parent de la barre (le masquer avec le focus encore dedans fait tomber le focus sur <body>) ; et ne jamais appeler preventDefault sur Tab, qui doit quitter la barre entière.
À ne pas faire :
<!-- À ne pas faire : role="menubar" sur la navigation du site.
Le lecteur d'écran annonce « barre de menus », entre en mode application,
et les touches de lecture cessent de fonctionner. Les flèches promises ne
font rien sur des liens. Pire que l'absence totale d'ARIA.
Ceci est un <nav><ul><li><a>. -->
<div role="menubar" aria-label="Principale">
<a role="menuitem" href="/">Accueil</a>
<a role="menuitem" href="/a-propos">À propos</a>
<a role="menuitem" href="/contact">Contact</a>
</div>
Défauts fréquents et impact utilisateur
role="menubar"sur la navigation du site : mode application forcé, touches de lecture perdues, flèches inopérantes sur des liens. L'erreur emblématique du motif.- Sous-menus au survol uniquement (CSS
:hover) : un utilisateur clavier ne peut jamais les ouvrir. - Tous les éléments de la barre en
tabindex="0": la barre promet un arrêt de tabulation unique et en impose autant que d'éléments. - Masquer un sous-menu alors que le focus est encore dedans : le focus tombe sur
<body>et l'utilisateur est renvoyé en haut de page ; il faut restaurer le focus sur l'élément parent. aria-expandedposé sur le sous-menu au lieu de l'élément déclencheur : aucun état annoncé.- État coché d'un
menuitemcheckboxsignalé par la seule couleur : imperceptible pour un utilisateur qui ne distingue pas les couleurs.
Ce que les outils automatiques ne détectent pas
Un scanner automatique, le nôtre compris, repère des enfants invalides dans un role="menu" et des menus sans nom accessible. Mais l'essentiel du motif lui échappe :
- une barre de menus posée sur une navigation de site est de l'ARIA valide et une régression d'accessibilité en même temps ; seul le jugement (« les éléments naviguent-ils ? ») la détecte ;
- le parcours clavier à deux dimensions (un seul arrêt de tabulation, focus sur le premier élément à l'ouverture, retour au parent à Échap) ne se vérifie qu'à la main ;
- le scénario « ouvrir un sous-menu, Échap, puis Tab » : si le focus est retombé sur
<body>, le sous-menu a été masqué avec le focus dedans, et aucun outil ne le signale ; - l'ouverture des sous-menus au clavier, et pas seulement au survol, reste une vérification manuelle.
Vérifier ce composant
Protocole manuel, trois minutes :
- La seule question qui compte le plus souvent : est-ce une barre de menus, tout court ? Si les éléments naviguent vers d'autres pages, ce n'en est pas une, et le correctif est de retirer l'ARIA, pas d'en ajouter.
- Au clavier seul : la barre est un seul arrêt de tabulation ; gauche et droite la parcourent ; bas ouvre un sous-menu et le focus atterrit sur son premier élément ; Échap ferme et rend le focus à l'élément parent ; Tab sort entièrement.
- Le test du focus perdu : ouvrez un sous-menu, Échap, puis Tab. Si vous repartez du haut de la page, le sous-menu a été masqué avec le focus encore dedans.
- Au lecteur d'écran : attendez-vous à « Éditeur, barre de menus » puis « Fichier, élément de menu, sous-menu, réduit ». Si vous entendez « lien », ce sont des
<a>: c'est une navigation.
Deux outils gratuits du site pour appuyer la vérification : le simulateur de lecteur d'écran affiche l'arbre d'accessibilité, où les rôles menubar / menu / menuitem et les états aria-expanded et aria-checked se lisent directement ; le testeur de focus visible valide vos styles :focus-visible, sollicités à chaque déplacement puisque le focus réel bouge dans toute la barre.
Approfondir avec les fiches critères
Vérifier ce composant sur votre site ?
Le scan repère les défauts détectables automatiquement (rôles incohérents, champs sans étiquette, attributs ARIA orphelins) ; le reste se vérifie à la main avec les protocoles de cette fiche.
Lancer un scan gratuit