Composant · Menus et navigation

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

Interaction clavier attendue

ToucheAction
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 / EspaceOuvre le sous-menu ou active l'élément
Début / FinPremier / dernier élément
ÉchapFerme le sous-menu et rend le focus à l'élément parent de la barre
TabQuitte 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-expanded posé sur le sous-menu au lieu de l'élément déclencheur : aucun état annoncé.
  • État coché d'un menuitemcheckbox signalé 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 :

  1. 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.
  2. 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.
  3. 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.
  4. 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

Toutes les fiches composants accessibles