# Un menu qui se ferme tout seul au clavier : un gestionnaire de clic mal placé

> Impossible d'ouvrir le sous-menu au clavier sans qu'il ne se referme aussitôt. Le coupable : un écouteur d'événement posé au mauvais endroit du DOM.

- Auteur : Clément Hadrot
- Publié le : 2022-04-20
- Mis à jour le : 2022-04-20
- Catégorie : Accessibilité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/accessibilite/menu-se-ferme-seul-clavier-gestionnaire-clic/

## L’essentiel

- Le focus déclenchait un clic fantôme sur le document
- Un seul document.addEventListener mal ciblé suffisait
- La correction tient en une vérification de la cible de l'événement

Un client nous a signalé un comportement agaçant sur le menu principal de son site vitrine : au clavier, en appuyant sur Entrée pour ouvrir un sous-menu, celui-ci s'affichait puis se refermait immédiatement, comme si une seconde touche invisible venait annuler l'action. À la souris, tout fonctionnait parfaitement. Le bug ne concernait donc que la navigation clavier, ce qui le rendait particulièrement difficile à reproduire pour quelqu'un qui teste toujours son site à la souris.

Ce billet détaille la démarche de diagnostic qui nous a menés jusqu'à la ligne de code fautive, puis la correction que nous avons appliquée pour que le menu reste ouvert le temps nécessaire à sa consultation.

## Symptôme : une fermeture instantanée et silencieuse

En inspectant le comportement pas à pas avec les outils de développement du navigateur, nous avons observé que l'attribut `aria-expanded` du bouton passait bien de `false` à `true` pendant une fraction de seconde, avant de repasser immédiatement à `false`. Le sous-menu n'était donc pas bloqué : il s'ouvrait puis se refermait dans la même frappe.

Aucune erreur n'apparaissait dans la console. Le comportement semblait volontaire du point de vue du navigateur, ce qui nous a orientés vers un problème de logique applicative plutôt que vers un bug de rendu ou de CSS.

## Diagnostic : un clic fantôme déclenché par le focus

Le thème utilisait un script maison pour fermer le menu lorsqu'on cliquait en dehors de lui, un pattern très courant :

```
document.addEventListener('click', function (event) {
  if (!menu.contains(event.target)) {
    menu.classList.remove('is-open');
    toggleButton.setAttribute('aria-expanded', 'false');
  }
});
```

> L'essentiel à retenir : Le focus déclenchait un clic fantôme sur le document ; Un seul document.addEventListener mal ciblé suffisait ; La correction tient en une vérification de la cible de l'événement

Ce code semble raisonnable, mais il ignorait un détail : sur certains navigateurs, activer un bouton avec la touche Entrée déclenche un événement `click` synthétique juste après l'ouverture du menu, avec une cible d'événement qui, selon l'ordre d'exécution des scripts, pouvait être interprétée comme extérieure au conteneur du menu si celui-ci n'était pas encore repositionné dans le DOM au moment de l'évaluation. Le gestionnaire de fermeture s'exécutait donc dans la foulée du gestionnaire d'ouverture, sur le même événement clavier.

## Correctif : distinguer l'origine de l'interaction

La solution retenue a consisté à ignorer l'événement de fermeture lorsqu'il provient du bouton d'ouverture lui-même, et à retarder l'écoute globale d'une frame pour laisser le temps au menu de se stabiliser dans le DOM avant d'activer la détection de clic extérieur :

```
toggleButton.addEventListener('click', function (event) {
  event.stopPropagation();
  const isOpen = menu.classList.toggle('is-open');
  toggleButton.setAttribute('aria-expanded', String(isOpen));
});

document.addEventListener('click', function (event) {
  if (event.target === toggleButton) {
    return;
  }
  if (!menu.contains(event.target)) {
    menu.classList.remove('is-open');
    toggleButton.setAttribute('aria-expanded', 'false');
  }
});
```

Le `event.stopPropagation()` posé sur le bouton d'ouverture empêche l'événement de remonter jusqu'à l'écouteur global du document pendant la même interaction. Une fois ce changement appliqué, le sous-menu est resté ouvert au clavier aussi longtemps qu'à la souris.

## Vérification avec les technologies d'assistance

Nous avons rejoué le scénario avec NVDA sous Firefox et VoiceOver sous Safari. Dans les deux cas, l'ouverture au clavier restait stable, et l'attribut `aria-expanded` restait cohérent avec l'état visuel du menu, ce qui est essentiel : un lecteur d'écran annonce cet état à l'utilisateur, et un décalage entre les deux aurait continué à tromper les personnes non-voyantes même après correction visuelle.

- Ouverture au clavier stable sur trois niveaux de sous-menu imbriqués
- Fermeture correcte avec la touche Échap
- Aucune régression constatée sur le comportement à la souris ou au tactile

## Prévention pour les prochains menus

Ce type de bug se cache facilement parce qu'il ne se manifeste qu'au clavier, un mode d'interaction rarement testé en recette classique. Nous avons ajouté à notre check-list de recette un test systématique : ouvrir chaque menu déroulant au clavier, avec Tab puis Entrée, avant toute livraison. Nous recommandons aussi d'éviter les écouteurs globaux sur `document` quand un écouteur ciblé sur le conteneur du menu, combiné à `event.stopPropagation()`, suffit largement et réduit la surface de bugs de ce genre.

La leçon la plus utile de ce cas reste la suivante : un bug invisible à la souris n'est pas un bug mineur. Il est simplement invisible à la personne qui ne teste jamais autrement qu'à la souris, ce qui, statistiquement, exclut une partie non négligeable des visiteurs réels du site.
