Le brief de reprise était clair : un site e-commerce de matériel de bricolage, un mega-menu à quatre colonnes déjà en production depuis deux ans, et une seule consigne du client — ne rien casser visuellement, le menu doit rester identique au pixel près. La contrainte usuelle du « je refais tout proprement » n’était pas sur la table : il fallait adapter l’existant, colonne par colonne, sans toucher au CSS de mise en page.
Le mega-menu original ne s’ouvrait qu’au survol de la souris, via un simple :hover en CSS, complété d’un peu de JavaScript pour gérer les appareils tactiles. Au clavier, appuyer sur Tab passait directement du premier lien du menu principal au second, sans jamais ouvrir les sous-menus : leur contenu, bien réel dans le DOM, restait invisible et inatteignable pour un utilisateur au clavier seul.
Étape 1 : cartographier la structure existante
Le HTML du mega-menu, une fois inspecté, suivait une structure raisonnable — une liste de <li> de premier niveau, chacun contenant un lien puis un <div> de sous-menu masqué par défaut. La logique d’ouverture, elle, était entièrement pilotée par CSS :hover, sans le moindre attribut ARIA ni gestion de touche.
<li class="menu-item-a-mega">
<a href="/outillage/">Outillage</a>
<div class="sous-menu">
<!-- quatre colonnes de liens -->
</div>
</li>
Étape 2 : ajouter l’état ouvert/fermé sans toucher au CSS de mise en page

Plutôt que de réécrire les règles :hover déjà validées visuellement, nous avons ajouté une classe d’état pilotée par JavaScript, avec les attributs ARIA nécessaires sur le lien déclencheur :
<a href="/outillage/" aria-expanded="false" aria-haspopup="true">Outillage</a>
document.querySelectorAll('.menu-item-a-mega > a').forEach((lien) => {
lien.addEventListener('keydown', (event) => {
if (event.key === 'ArrowDown') {
event.preventDefault();
ouvrirSousMenu(lien);
lien.parentElement.querySelector('.sous-menu a').focus();
}
if (event.key === 'Escape') {
fermerSousMenu(lien);
lien.focus();
}
});
});
function ouvrirSousMenu(lien) {
lien.setAttribute('aria-expanded', 'true');
lien.parentElement.classList.add('sous-menu-ouvert');
}
function fermerSousMenu(lien) {
lien.setAttribute('aria-expanded', 'false');
lien.parentElement.classList.remove('sous-menu-ouvert');
}
La classe sous-menu-ouvert reproduit en JavaScript exactement ce que faisait :hover visuellement, ce qui a permis de ne modifier aucune règle de mise en page existante : seule la condition de déclenchement change.
Étape 3 : parcourir les quatre colonnes au Tab, puis en sortir
Une fois le sous-menu ouvert, l’utilisateur doit pouvoir tabuler à travers l’ensemble des liens des quatre colonnes, dans un ordre de lecture cohérent (colonne par colonne, de haut en bas), puis sortir naturellement du sous-menu en continuant à appuyer sur Tab une fois le dernier lien atteint — sans piège de focus, puisqu’un mega-menu n’est pas une fenêtre modale.
Étape 4 : fermer proprement quand le focus quitte le menu
Un dernier ajustement a été nécessaire : quand l’utilisateur quitte le sous-menu par Tab vers l’élément suivant de la page (et non vers Échap), le sous-menu doit se refermer visuellement, sinon il reste ouvert à l’écran alors que le focus n’y est plus. Un écouteur sur l’événement focusout, avec une vérification que le nouveau focus ne se trouve plus dans le sous-menu, referme ce dernier proprement.
Ce que la reprise nous a coûté, et ce qu’elle a évité
- Environ une journée de développement pour les quatre colonnes, contre plusieurs jours estimés pour une reconstruction complète du mega-menu.
- Aucune régression visuelle signalée en recette, puisque le CSS de mise en page n’a pas été touché.
- Une dette technique documentée : la structure HTML reste perfectible, mais elle est désormais utilisable, ce qui était la priorité du client à ce stade du projet.
En résumé
Reprendre un mega-menu existant plutôt que le reconstruire est souvent possible en ajoutant une couche d’état ARIA et de gestion de touche par-dessus une logique :hover déjà en place, à condition de bien cartographier la structure avant de toucher au moindre script. Le résultat n’est pas toujours l’architecture la plus élégante, mais il répond à la contrainte réelle du projet : rendre utilisable sans tout casser.