frontend / html
Accessibilité : gestion du focus niveau expert
Explication
Ce que vous allez apprendre
- Ajouter un lien d'évitement ("skip link") pour sauter directement au contenu principal
- Comprendre pourquoi un
tabindexpositif casse l'ordre naturel de tabulation - Piéger le focus à l'intérieur d'une modale ouverte et le restaurer à la fermeture
- Remplacer
outline: nonepar:focus-visiblesans sacrifier l'accessibilité clavier - Distinguer
tabindex="0",tabindex="-1"ettabindexpositif
Dans quel contexte ?
Lors d'un audit d'accessibilité RGAA sur checkout.html, l'auditeur ouvre la modale de confirmation de commande au clavier puis appuie sur Tab : le focus sort de la modale et continue sur les liens du fond de page, invisibles derrière l'overlay. L'utilisateur se retrouve complètement perdu, sans repère visuel. Cette leçon montre comment implémenter un vrai piège de focus (focus trap) pour que la modale reste l'unique zone accessible tant qu'elle est ouverte.
Étape 1 : qui navigue sans souris
D'abord, un constat : beaucoup d'utilisateurs n'utilisent jamais de souris. Des personnes malvoyantes avec un lecteur d'écran, des personnes à mobilité réduite utilisant uniquement le clavier, ou simplement des "power users" qui préfèrent Tab à la souris.
Étape 2 : pourquoi le focus est leur seul repère
Pour ces utilisateurs, le focus clavier — l'indicateur visuel de "où je suis" sur la page — est leur seul repère. Une page qui le gère mal peut littéralement rendre certaines fonctionnalités inutilisables pour eux.
Étape 3 : le premier problème concret, traverser le menu
Imaginez devoir appuyer sur Tab quinze fois à chaque page pour traverser tout le menu de navigation avant d'atteindre le contenu principal. C'est l'expérience vécue par un utilisateur clavier sans "skip link".
Étape 4 : la solution, le lien d'évitement
Ce lien, invisible par défaut et qui n'apparaît qu'au focus, permet de sauter directement au contenu principal — un gain de temps énorme répété à chaque page visitée.
Étape 5 : le piège du tabindex positif
Il reste une erreur fréquente à éviter : croire que tabindex="1", tabindex="2", etc. permettent de "programmer" l'ordre de tabulation. En réalité, cela casse l'ordre naturel du DOM et crée des parcours imprévisibles.
La bonne pratique consiste à laisser l'ordre du DOM piloter la tabulation, et à réorganiser le DOM lui-même si l'ordre doit changer.
Valeur de tabindex | Effet | À utiliser quand |
|---|---|---|
0 | Rend focusable dans l'ordre naturel du DOM | Élément interactif non nativement focusable |
-1 | Focusable seulement via JS ou lien d'ancre, jamais via Tab | Cible d'un skip-link, conteneur de modale |
Positif (1, 2...) | Force un ordre de tabulation arbitraire | Jamais — casse l'ordre naturel du DOM |
Piège fréquent
tabindex="1" semble "logique" pour dire "ce champ en premier", mais dès qu'un deuxième développeur ajoute un champ avec tabindex="1" ailleurs sur la page, l'ordre devient imprévisible et incohérent avec l'ordre visuel. Ne touchez jamais à tabindex avec une valeur positive.
Étape 6 : le piège le plus dangereux, outline: none
Enfin, l'erreur la plus grave : supprimer l'anneau de focus par défaut avec outline: none sans le remplacer. Cela rend la navigation clavier totalement invisible, donc inutilisable en pratique, même si elle fonctionne techniquement.
La pseudo-classe :focus-visible résout élégamment ce problème : elle n'affiche l'anneau qu'à la navigation clavier, jamais au clic souris.
Bonne pratique
Remplacez tout *:focus { outline: none; } trouvé dans une feuille de style existante par :focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; }. Le rendu reste propre au clic souris, tout en restaurant un repère visuel clair pour la navigation clavier.
Et ensuite ?
Une fois le focus maîtrisé, changeons complètement de terrain : la prochaine leçon aborde le SEO technique, à commencer par les meta tags et Open Graph.
Commandes & code
Accessibilité : gestion du focus niveau expert
Un utilisateur clavier doit toujours savoir où il se trouve et ne jamais être piégé.
<!-- Lien d'évitement ("skip link") : premier élément focusable de la page -->
<body>
<a class="skip-link" href="#contenu-principal">Aller au contenu principal</a>
<header>...</header>
<main id="contenu-principal" tabindex="-1">
<!-- tabindex="-1" : focusable par script/lien d'ancre, MAIS pas via Tab -->
<h1>Titre de la page</h1>
</main>
</body>
<style>
.skip-link {
position: absolute;
left: -9999px; /* hors écran par défaut */
}
.skip-link:focus {
left: 0; /* réapparaît visuellement dès que focus clavier */
top: 0;
z-index: 999;
background: #000;
color: #fff;
padding: 8px 16px;
}
</style><!-- Ordre de tabulation : ne JAMAIS forcer avec des valeurs positives -->
<input type="text" tabindex="1"> <!-- MAUVAIS : casse l'ordre DOM naturel -->
<input type="text" tabindex="0"> <!-- OK : rend focusable un élément qui ne l'est pas nativement -->
<input type="text" tabindex="-1"> <!-- OK : focusable en JS uniquement -->
<!-- L'ordre DOM EST l'ordre de tabulation par défaut : réorganiser le DOM,
pas ajouter des tabindex numériques. -->// Trap focus dans une modale : pattern expert incontournable
class Modale {
constructor(element) {
this.element = element;
this.dernierFocus = null;
}
ouvrir() {
this.dernierFocus = document.activeElement; // pour restaurer à la fermeture
this.element.hidden = false;
const elementsFocusables = this.element.querySelectorAll(
'a[href], button:not([disabled]), input, select, textarea, [tabindex="0"]'
);
this.premier = elementsFocusables[0];
this.dernier = elementsFocusables[elementsFocusables.length - 1];
this.premier.focus();
this.element.addEventListener("keydown", this.gererTouches);
}
gererTouches = (e) => {
if (e.key === "Escape") {
this.fermer();
return;
}
if (e.key !== "Tab") return;
// piège le Tab à l'intérieur de la modale
if (e.shiftKey && document.activeElement === this.premier) {
e.preventDefault();
this.dernier.focus();
} else if (!e.shiftKey && document.activeElement === this.dernier) {
e.preventDefault();
this.premier.focus();
}
};
fermer() {
this.element.hidden = true;
this.element.removeEventListener("keydown", this.gererTouches);
this.dernierFocus?.focus(); // restaure le focus initial : crucial
}
}/* Ne JAMAIS supprimer l'anneau de focus sans le remplacer */
/* MAUVAIS : rend le clavier inutilisable */
*:focus { outline: none; }
/* BON : style custom cohérent avec le design */
:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
/* :focus-visible : n'apparaît qu'au clavier, pas au clic souris —
le meilleur des deux mondes */Résumé
- Un skip-link doit être le tout premier élément focusable du
<body>. - L'ordre de tabulation suit le DOM ; ne jamais utiliser
tabindexpositif. - Une modale doit piéger le focus (Tab cyclique) et restaurer le focus initial à la fermeture.
:focus-visibleremplaceoutline: noneproprement, sans casser l'accessibilité clavier.
Exercices pratiques
Mission : corriger l'audit RGAA de checkout.html
Objectif : Diagnostiquer une modale sans piège de focus et une feuille de style qui supprime l'anneau de focus, puis proposer les corrections.
Contexte
Lors d'un audit RGAA sur checkout.html, l'auditeur ouvre la modale de confirmation de commande au clavier, puis appuie sur Tab : le focus sort de la modale et continue sur les liens du fond de page, invisibles derrière l'overlay. L'auditeur note aussi que la feuille de style globale contient *:focus { outline: none; }, sans aucun remplacement.
Ta mission : corriger l'entrée dans le contenu principal et diagnostiquer les deux problèmes de focus.