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.
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ème | Impact |
|---|---|
| Ouverture au survol uniquement | Inaccessible au clavier et tactile |
| Pas de gestion du focus | Utilisateurs perdus dans la page |
| Structure non sémantique | Lecteurs d'écran confus |
| Trop de niveaux | Charge cognitive excessive |
| Pas de fermeture Échap | Utilisateur 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
<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
<nav>avecaria-labelpour identifier la navigation<ul>/<li>pour la structure de liste<button>(pas<a>) pour les déclencheurs de sous-menusaria-expandedpour indiquer l'étataria-controlspour lier au panneauhiddensur les panneaux fermés
Contenu du méga-menu
Organisez le contenu en colonnes thématiques :
<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. Utilisezaria-labelledbysur les listes pour les associer.
Navigation clavier
Comportements attendus
| Touche | Action |
|---|---|
| Tab | Passe à l'élément focusable suivant |
| Entrée/Espace | Ouvre/ferme le sous-menu (sur bouton) |
| Échap | Ferme 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
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.
<!-- 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 :
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 :
@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 :
- Entrer dans le sous-menu (Tab depuis le bouton)
- Naviguer dans le sous-menu
- Sortir naturellement (Tab après le dernier lien)
- 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 :
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é :
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 :
- Clavier seul : Naviguez tout le menu sans souris
- Lecteur d'écran : Les états sont annoncés ?
- Mobile : Le menu fonctionne au toucher ?
- Zoom 200% : Le menu reste utilisable ?
- Échap : Ferme bien le menu ?
Erreurs courantes
1. Liens déclencheurs au lieu de boutons
<!-- 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
/* 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ère | Exigence |
|---|---|
| 7.1 | Scripts compatibles avec les technologies d'assistance |
| 7.3 | Composants utilisables au clavier |
| 12.6 | Zones de regroupement de liens |
| 12.8 | Ordre 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.
Guides RGAA associés
Pour aller plus loin sur les sujets abordés dans cet article, consultez nos fiches techniques :
Chaque champ de formulaire doit avoir une étiquette (label) qui lui est liée explicitement.
Chaque image porteuse d'information doit avoir une alternative textuelle pertinente via l'attribut alt. Les images décoratives doivent avoir un attribut alt vide.
L'ordre de tabulation clavier doit être cohérent avec l'ordre de lecture visuel et ne pas piéger le focus.
Articles similaires
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.