accessibility
Bonnes pratiques tabindex
Signale les valeurs de tabindex positives et l'utilisation excessive de tabindex="-1". Les valeurs positives cassent l'ordre du DOM ; seuls 0 et -1 ont leur place en production.
Ce que vérifie ce contrôle
Analyse tous les éléments ayant un attribut tabindex et remonte deux problèmes :
- Valeurs positives (
tabindex="1","2", etc.) — elles écrasent l’ordre de tabulation naturel du DOM et causent presque toujours des bugs de focus. - Surutilisation de
tabindex="-1"sur des éléments qui devraient être accessibles au clavier — erreur fréquente sur les boutons et liens personnalisés.
Le contrôle note aussi quand tabindex est appliqué à des éléments nativement focusables (<a href>, <button>, <input>), où c’est généralement redondant.
Pourquoi c’est important
Un tabindex positif crée un univers clavier parallèle. Le navigateur tabule d’abord à travers toutes les valeurs positives — par ordre numérique — puis retombe sur l’ordre du DOM pour le reste. Ajoutez tabindex="1" à un élément et vous venez de le promouvoir devant chaque lien, bouton et champ de formulaire de la page. Ajoutez-le à dix éléments et vous avez renuméroté manuellement l’ordre de tabulation, ce qui signifie que chaque future modification (un nouvel élément de menu, un champ de formulaire échangé) casse silencieusement la séquence.
WCAG 2.4.3 (Ordre du focus) exige que le focus se déplace dans un ordre qui préserve sens et opérabilité. L’ordre visuel doit correspondre à l’ordre de tabulation. Un tabindex positif viole presque toujours cela — les utilisateurs clavier et de lecteurs d’écran atterrissent à des endroits surprenants, perdent le contexte et abandonnent.
tabindex="-1" a le problème inverse quand on en abuse : il retire totalement l’élément de la séquence de tabulation. Appliquez-le à un bouton et les utilisateurs clavier ne peuvent plus l’atteindre.
Comment corriger
Règle d’or : dans le code applicatif normal, les seules valeurs acceptables sont tabindex="0" et tabindex="-1", et vous n’en avez besoin que sur des éléments non natifs.
Quand tabindex="0" est correct
Un widget interactif personnalisé que le navigateur ne focalise pas par défaut :
<!-- Onglet personnalisé dans un tablist -->
<div role="tab" tabindex="0" aria-selected="true">Aperçu</div>
<!-- Carte qui ouvre une modale au clic -->
<div role="button" tabindex="0" onclick="openModal()">
Voir les détails
</div>
Préférez un vrai <button> quand vous le pouvez — il vous donne gratuitement le focus, l’activation Entrée/Espace et la sémantique pour lecteurs d’écran. Recourez à tabindex="0" uniquement quand l’élément natif ne convient pas.
Quand tabindex="-1" est correct
Une cible de focus programmatique — un élément que vous allez focaliser via JavaScript mais qui ne doit pas apparaître dans la séquence de tabulation :
<!-- Résumé d'erreurs, focalisé à la soumission du formulaire -->
<div id="errors" tabindex="-1" role="alert">
3 champs nécessitent votre attention
</div>
<!-- Conteneur de modale, focalisé à l'ouverture -->
<div id="modal" tabindex="-1" role="dialog" aria-modal="true">
...
</div>
<!-- Cible de lien d'évitement (contenu principal) -->
<main id="main" tabindex="-1">...</main>
Puis en JS :
document.getElementById("errors").focus();
Le -1 permet à .focus() de fonctionner sans insérer l’élément dans l’ordre de tabulation.
Quand un tabindex positif est correct
Presque jamais. Le seul cas défendable est l’intégration avec un formulaire legacy où vous ne pouvez pas réorganiser le DOM et où l’ordre visuel exige un re-séquencement — et même là, corriger le DOM est la bonne réponse.
Lintez-le en CI
Attrapez-le avant la revue :
bun add -D eslint-plugin-jsx-a11y
// eslint.config.js
import jsxA11y from "eslint-plugin-jsx-a11y";
export default [{
plugins: { "jsx-a11y": jsxA11y },
rules: {
"jsx-a11y/tabindex-no-positive": "error",
"jsx-a11y/no-noninteractive-tabindex": "warn",
},
}];
Pour les dépôts HTML / template vanilla, axe-core attrape le même problème au moment de l’audit — voir échecs d’accessibilité Lighthouse.
Corriger une page qui a déjà un tabindex positif
- Supprimez chaque attribut
tabindex="1+". - Tabulez à travers la page au clavier. Notez les sauts surprenants.
- Réorganisez le DOM pour que la séquence naturelle corresponde à la visuelle. N’utilisez le CSS (
order, placement grid) que pour un repositionnement visuel qui n’affecte pas l’ordre de lecture. - Relancez l’audit.
Questions fréquentes
Puis-je utiliser tabindex="0" pour rendre un <div> cliquable ?
Vous pouvez, mais ajouter role="button" et des handlers Entrée/Espace, c’est la moitié du travail — un vrai <button> est un seul élément avec tout déjà câblé. Le motif tabindex="0" + role="button" est réservé aux cas où vous avez besoin d’un élément non-bouton (par ex. un treeitem, un onglet) que la spec n’inclut pas comme nativement focusable.
tabindex="-1" cache-t-il l’élément aux lecteurs d’écran ?
Non. tabindex="-1" retire seulement l’élément de la navigation clavier séquentielle. Les lecteurs d’écran peuvent toujours l’atteindre via leur propre navigation (titres, repères, curseur virtuel). Pour cacher aux technologies d’assistance, utilisez aria-hidden="true" — mais jamais sur un élément interactif.
Et le roving tabindex dans un menu ou une grille ?
C’est le bon motif pour les widgets composites (menus, tablists, grilles). Exactement un élément a tabindex="0", les autres ont tabindex="-1", et les touches fléchées déplacent le focus en basculant les valeurs. Le contrôle en tient compte : un seul 0 plus plusieurs -1 à l’intérieur du même role="menu" / role="tablist" / role="grid" n’est pas signalé.
Sources
Dernière mise à jour 2026-05-11