Aller au contenu principal
Technique21 mars 20269 min

Méga-menus accessibles : Guide d'implémentation complet

En Bref : L'essentiel à retenir

  • Les méga-menus doivent s'ouvrir au clic ou à l'appui clavier, pas uniquement au survol souris qui exclut les utilisateurs clavier et mobiles.
  • La structure utilise nav avec ul/li sémantiques et aria-expanded sur les boutons déclencheurs, pas role="menu" qui est réservé aux menus applicatifs.
  • La touche Échap doit fermer le méga-menu et redonner le focus au bouton déclencheur pour une navigation fluide.
  • Les sous-menus profondément imbriqués augmentent la charge cognitive et compliquent la navigation clavier : limitez la profondeur à 2 niveaux.
NavigationMéga-menuARIAClavierAccessibilité

Les méga-menus sont courants sur les sites e-commerce et les portails riches en contenu. Mais leur complexité crée souvent des barrières d'accessibilité majeures. Voici comment les implémenter correctement.

Les défis des méga-menus

Un méga-menu mal conçu pose plusieurs problèmes :

ProblèmeImpact
Ouverture au survol uniquementInaccessible au clavier et tactile
Pas de gestion du focusUtilisateurs perdus dans la page
Structure non sémantiqueLecteurs d'écran confus
Trop de niveauxCharge cognitive excessive
Pas de fermeture ÉchapUtilisateur piégé

[!WARNING] Les méga-menus qui s'ouvrent uniquement au survol souris sont un échec d'accessibilité majeur. Ils excluent les utilisateurs clavier, les écrans tactiles et les personnes avec des tremblements.

Structure HTML sémantique

Le conteneur de navigation

CODE
<nav aria-label="Menu principal">
  <ul class="mega-menu">
    <li>
      <a href="/accueil">Accueil</a>
    </li>
    <li class="has-submenu">
      <button
        aria-expanded="false"
        aria-controls="submenu-produits">
        Produits
        <svg aria-hidden="true" class="icon-arrow">...</svg>
      </button>
      <div id="submenu-produits" class="submenu" hidden>
        <!-- Contenu du méga-menu -->
      </div>
    </li>
    <li>
      <a href="/contact">Contact</a>
    </li>
  </ul>
</nav>

Points clés de la structure

  1. <nav> avec aria-label pour identifier la navigation
  2. <ul>/<li> pour la structure de liste
  3. <button> (pas <a>) pour les déclencheurs de sous-menus
  4. aria-expanded pour indiquer l'état
  5. aria-controls pour lier au panneau
  6. hidden sur les panneaux fermés

Contenu du méga-menu

Organisez le contenu en colonnes thématiques :

CODE
<div id="submenu-produits" class="submenu" hidden>
  <div class="submenu-inner">
    <div class="submenu-column">
      <h3 id="cat-vetements">Vêtements</h3>
      <ul aria-labelledby="cat-vetements">
        <li><a href="/produits/chemises">Chemises</a></li>
        <li><a href="/produits/pantalons">Pantalons</a></li>
        <li><a href="/produits/vestes">Vestes</a></li>
      </ul>
    </div>

    <div class="submenu-column">
      <h3 id="cat-accessoires">Accessoires</h3>
      <ul aria-labelledby="cat-accessoires">
        <li><a href="/produits/ceintures">Ceintures</a></li>
        <li><a href="/produits/montres">Montres</a></li>
      </ul>
    </div>

    <div class="submenu-promo">
      <a href="/promo">
        <img src="promo.jpg" alt="Soldes : jusqu'à -50%">
      </a>
    </div>
  </div>
</div>

[!NOTE] Les titres <h3> dans le méga-menu aident à structurer visuellement ET pour les lecteurs d'écran. Utilisez aria-labelledby sur les listes pour les associer.

Comportements attendus

ToucheAction
TabPasse à l'élément focusable suivant
Entrée/EspaceOuvre/ferme le sous-menu (sur bouton)
ÉchapFerme le sous-menu, focus sur le bouton
Flèche bas(Optionnel) Ouvre le sous-menu
Flèche droite/gauche(Optionnel) Navigation entre items de niveau 1

Implémentation JavaScript

CODE
class MegaMenu {
  constructor(nav) {
    this.nav = nav;
    this.triggers = nav.querySelectorAll('[aria-expanded]');
    this.currentOpen = null;

    this.init();
  }

  init() {
    this.triggers.forEach(trigger => {
      trigger.addEventListener('click', () => this.toggle(trigger));
      trigger.addEventListener('keydown', (e) => this.handleKeydown(e, trigger));
    });

    // Fermer au clic extérieur
    document.addEventListener('click', (e) => {
      if (!this.nav.contains(e.target)) {
        this.closeAll();
      }
    });

    // Fermer avec Échap
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && this.currentOpen) {
        this.close(this.currentOpen);
        this.currentOpen.focus();
      }
    });
  }

  toggle(trigger) {
    const isOpen = trigger.getAttribute('aria-expanded') === 'true';

    // Fermer les autres
    this.closeAll();

    if (!isOpen) {
      this.open(trigger);
    }
  }

  open(trigger) {
    const submenu = document.getElementById(
      trigger.getAttribute('aria-controls')
    );

    trigger.setAttribute('aria-expanded', 'true');
    submenu.hidden = false;
    this.currentOpen = trigger;

    // Focus sur le premier lien du sous-menu
    const firstLink = submenu.querySelector('a');
    if (firstLink) {
      firstLink.focus();
    }
  }

  close(trigger) {
    const submenu = document.getElementById(
      trigger.getAttribute('aria-controls')
    );

    trigger.setAttribute('aria-expanded', 'false');
    submenu.hidden = true;
    this.currentOpen = null;
  }

  closeAll() {
    this.triggers.forEach(trigger => this.close(trigger));
  }

  handleKeydown(event, trigger) {
    const isOpen = trigger.getAttribute('aria-expanded') === 'true';

    switch (event.key) {
      case 'ArrowDown':
        if (!isOpen) {
          event.preventDefault();
          this.open(trigger);
        }
        break;

      case 'Escape':
        if (isOpen) {
          event.preventDefault();
          this.close(trigger);
          trigger.focus();
        }
        break;
    }
  }
}

// Initialisation
document.querySelectorAll('nav').forEach(nav => {
  if (nav.querySelector('.mega-menu')) {
    new MegaMenu(nav);
  }
});

Pourquoi pas role="menu" ?

Le rôle ARIA menu est destiné aux menus applicatifs (comme les menus de logiciels), pas aux menus de navigation web. Il impose des comportements clavier spécifiques qui ne correspondent pas aux attentes des utilisateurs web.

CODE
<!-- Incorrect pour une navigation web -->
<ul role="menu">
  <li role="menuitem"><a href="/">Accueil</a></li>
</ul>

<!-- Correct : navigation standard -->
<nav aria-label="Menu principal">
  <ul>
    <li><a href="/">Accueil</a></li>
  </ul>
</nav>

[!TIP] Le WAI-ARIA Authoring Practices guide distingue "Navigation Menubar" (avec role="menubar") et "Disclosure Navigation Menu" (sans rôle menu). Ce dernier est généralement préférable pour les sites web.

Gestion du survol

Si vous souhaitez aussi ouvrir au survol (en complément du clic), ajoutez un délai pour éviter les ouvertures accidentelles :

CODE
let hoverTimeout;

trigger.addEventListener('mouseenter', () => {
  hoverTimeout = setTimeout(() => {
    this.open(trigger);
  }, 200); // Délai de 200ms
});

trigger.addEventListener('mouseleave', () => {
  clearTimeout(hoverTimeout);
  // Délai avant fermeture aussi
  setTimeout(() => {
    if (!submenu.matches(':hover')) {
      this.close(trigger);
    }
  }, 300);
});

Mais gardez toujours le déclenchement au clic/clavier comme méthode principale.

Responsive : menu mobile

Sur mobile, transformez le méga-menu en menu accordéon :

CODE
@media (max-width: 768px) {
  .mega-menu {
    flex-direction: column;
  }

  .submenu {
    position: static;
    width: 100%;
  }
}

Le comportement JavaScript reste identique : clic pour ouvrir/fermer.

Piège du focus

Quand le méga-menu est ouvert, le focus doit pouvoir :

  1. Entrer dans le sous-menu (Tab depuis le bouton)
  2. Naviguer dans le sous-menu
  3. Sortir naturellement (Tab après le dernier lien)
  4. Fermer avec Échap (retour au bouton)

Ne créez pas de "piège à focus" où l'utilisateur ne peut pas sortir.

Limiter la profondeur

Évitez les sous-sous-menus. Deux niveaux maximum :

CODE
Niveau 1 : Catégories principales (dans la barre)
Niveau 2 : Sous-catégories (dans le méga-menu)

Au-delà, la navigation devient confuse et la charge cognitive explose.

Indicateur d'état visuel

Montrez clairement l'état ouvert/fermé :

CODE
button[aria-expanded="true"] .icon-arrow {
  transform: rotate(180deg);
}

button[aria-expanded="true"] {
  background-color: #f0f0f0;
  border-bottom-color: transparent;
}

L'icône est décorative (aria-hidden="true"), l'état réel est communiqué par aria-expanded.

Test d'accessibilité

Vérifiez ces points :

  1. Clavier seul : Naviguez tout le menu sans souris
  2. Lecteur d'écran : Les états sont annoncés ?
  3. Mobile : Le menu fonctionne au toucher ?
  4. Zoom 200% : Le menu reste utilisable ?
  5. Échap : Ferme bien le menu ?

Erreurs courantes

1. Liens déclencheurs au lieu de boutons

CODE
<!-- Incorrect : le lien ne devrait pas déclencher ET naviguer -->
<a href="/produits" aria-expanded="false">Produits</a>

<!-- Correct : bouton pour déclencher, liens dans le menu -->
<button aria-expanded="false">Produits</button>

2. Pas de focus visible

CODE
/* Obligatoire */
.mega-menu a:focus,
.mega-menu button:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

3. Fermeture agressive au mouseout

Ne fermez pas le menu dès que la souris quitte le bouton. L'utilisateur doit pouvoir traverser vers le sous-menu.

Critères RGAA concernés

CritèreExigence
7.1Scripts compatibles avec les technologies d'assistance
7.3Composants utilisables au clavier
12.6Zones de regroupement de liens
12.8Ordre de tabulation cohérent

Conclusion

Un méga-menu accessible demande une attention particulière à la structure HTML, à la gestion du clavier et aux états ARIA. La règle d'or : tout ce qui fonctionne au survol doit aussi fonctionner au clic et au clavier. Testez systématiquement avec différents modes d'interaction pour garantir une expérience inclusive.

Analysez la navigation de votre site avec RGAA Checker : notre outil vérifie la structure des menus, les attributs ARIA et l'accessibilité clavier.

Votre site est-il conforme ?

Ne prenez pas de risques avec l'accessibilité. Lancez un audit complet de votre site en quelques minutes et obtenez un rapport détaillé des corrections à apporter.