Le WordPress d'aujourd'hui, décodé pour les développeurs

Accessibilité

Restaurer le focus au bon déclencheur avec matches() et closest() en JavaScript natif

Une recette sans dépendance pour retrouver l'élément qui a ouvert un panneau et lui rendre le focus à la fermeture, avec deux méthodes DOM natives.

Par WordPress Développement • 10 février 2020 • 5 min de lecture • Aucun commentaire
Restaurer le focus au bon déclencheur avec matches() et closest() en JavaScript natif

document.activeElement répond presque toujours à la question « où est le focus clavier en ce moment ? », sauf au moment précis où il faudrait s’en souvenir. Dès qu’un panneau latéral, un menu contextuel ou un tiroir de filtres se ferme, l’élément qui l’avait ouvert doit récupérer le focus. Sinon, le curseur clavier retombe sur le <body> et l’utilisateur doit tout retrouver à l’aveugle.

Cette recette ne suppose aucune bibliothèque de composants. Elle s’appuie sur deux méthodes du DOM disponibles nativement dans tous les navigateurs modernes : Element.matches() et Element.closest(). Elle convient à un panneau ouvert par un simple bouton, pas à une fenêtre modale construite avec une bibliothèque tierce, qui gère généralement son propre cycle de focus.

Le problème concret

Un bouton ouvre un panneau. L’utilisateur navigue dedans au clavier, puis ferme le panneau avec la touche Échap ou un bouton de fermeture. À cet instant, le nœud DOM qui avait le focus juste avant l’ouverture a souvent disparu de la mémoire du script : il n’a jamais été stocké, ou il l’a été dans une variable qui a été écrasée par une autre ouverture entre-temps.

Le symptôme est simple à observer : ouvrez les outils de développement, activez le suivi de document.activeElement dans la console, ouvrez puis fermez le panneau. Si le focus atterrit sur <body>, le problème est confirmé.

La méthode : retrouver le déclencheur par sa signature

Plutôt que de stocker une référence à chaque ouverture, une autre approche consiste à donner à chaque déclencheur un identifiant stable, puis à le retrouver au moment de fermer, via closest() pour remonter dans l’arbre si l’événement de fermeture part d’un élément imbriqué.

L'essentiel à retenir : Element.closest() retrouve l'ancêtre déclencheur ; Un data-attribute suffit à faire le lien ; Aucune bibliothèque n'est nécessaire
// Chaque bouton d'ouverture porte un attribut de liaison
// <button data-panel-target="filtres">Filtrer</button>

function ouvrirPanneau(panneau, declencheur) {
  panneau.hidden = false;
  panneau.dataset.openedBy = declencheur.dataset.panelTarget;
  const premierFocusable = panneau.querySelector('button, [href], input, select, textarea');
  if (premierFocusable) premierFocusable.focus();
}

function fermerPanneau(panneau) {
  const id = panneau.dataset.openedBy;
  panneau.hidden = true;
  const declencheur = document.querySelector(`[data-panel-target="${id}"]`);
  if (declencheur) declencheur.focus();
}

document.addEventListener('click', (evenement) => {
  const bouton = evenement.target.closest('[data-panel-target]');
  if (bouton) {
    const panneau = document.getElementById(bouton.dataset.panelTarget);
    ouvrirPanneau(panneau, bouton);
    return;
  }
  const fermeture = evenement.target.closest('[data-panel-close]');
  if (fermeture) {
    fermerPanneau(fermeture.closest('[hidden], .panneau)'));
  }
});

Pourquoi closest() plutôt qu’une simple correspondance

Element.matches() répond à une question binaire : cet élément précis correspond-il à ce sélecteur ? Element.closest() va plus loin, il remonte l’arbre du DOM depuis l’élément donné, en testant à chaque niveau matches(), jusqu’à trouver une correspondance ou atteindre la racine du document. C’est précisément ce qu’il faut quand le clic de fermeture part d’une icône SVG imbriquée dans un bouton, lui-même imbriqué dans un en-tête de panneau : l’événement se déclenche sur l’icône, mais c’est le bouton ou le panneau qui portent l’information utile.

  • evenement.target.matches('[data-panel-close]') échoue si le clic touche l’icône interne
  • evenement.target.closest('[data-panel-close]') retrouve le bon élément quel que soit le niveau de clic
  • La délégation d’événements sur document évite d’attacher un écouteur par panneau

Variante : mémoriser une pile d’ouvertures

Quand plusieurs panneaux peuvent s’empiler (un panneau qui ouvre lui-même un sous-panneau), un identifiant unique par déclencheur ne suffit plus : il faut une pile. Une variable de portée module, un tableau simple, accumule les déclencheurs successifs, et la fermeture dépile le dernier élément ajouté.

const pileDeFocus = [];

function ouvrir(panneau, declencheur) {
  pileDeFocus.push(declencheur);
  panneau.hidden = false;
}

function fermer(panneau) {
  panneau.hidden = true;
  const precedent = pileDeFocus.pop();
  if (precedent) precedent.focus();
}

Cette variante évite de dépendre d’un attribut data-* et fonctionne même si deux déclencheurs partagent la même cible visuelle, tant que l’ordre d’ouverture et de fermeture reste cohérent.

Ce que cette recette ne couvre pas

Les fenêtres modales construites avec une bibliothèque (un composant de dialogue tiers, un système de routing qui gère ses propres transitions) disposent en général de leur propre mécanisme de restauration du focus, parfois configurable via une prop dédiée. Appliquer cette recette par-dessus créerait un conflit : deux scripts tenteraient de déplacer le focus au même moment. Vérifiez toujours la documentation du composant avant d’ajouter une gestion manuelle.

De même, cette recette ne traite pas le piégeage du focus à l’intérieur du panneau ouvert (empêcher la tabulation de sortir du panneau tant qu’il est ouvert) : c’est un sujet séparé, avec ses propres pièges de conception.

Une règle simple à retenir dans un projet WordPress sur mesure : chaque fonction qui masque un élément (hidden, display: none, une classe qui déclenche une transition) devrait avoir sa fonction miroir qui restaure explicitement le focus. Ne comptez jamais sur le comportement par défaut du navigateur pour deviner où l’utilisateur doit atterrir.

En résumé

Restaurer le focus au bon endroit n’exige ni bibliothèque ni framework : deux méthodes natives du DOM, matches() et closest(), suffisent à retrouver de façon fiable l’élément qui a ouvert un panneau, même quand l’événement de fermeture part d’un nœud imbriqué plusieurs niveaux plus bas. La clé tient en une phrase : chaque ouverture doit savoir comment se refermer proprement, et cela commence par ne jamais perdre la trace du déclencheur.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi