accessibility

Ús correcte de tabindex

Marca valors positius de tabindex i l'abús de tabindex="-1". Els valors positius trenquen l'ordre del DOM; només 0 i -1 tenen lloc al marcat de producció.

Què comprova aquesta auditoria

Escaneja tots els elements amb un atribut tabindex i reporta dos problemes:

  1. Valors positius (tabindex="1", "2", etc.), aquests sobreescriuen l’ordre natural de tabulació del DOM i gairebé sempre causen errors de focus.
  2. Abús de tabindex="-1" en elements que haurien de ser accessibles per teclat, un error comú en botons i enllaços personalitzats.

La comprovació també nota quan s’aplica tabindex a elements que són focusables natiument (<a href>, <button>, <input>), on normalment és redundant.

Per què importa

Un tabindex positiu crea un univers de teclat paral·lel. El navegador recorre primer tots els valors positius, en ordre numèric, i després cau a l’ordre del DOM per a la resta. Afegeix tabindex="1" a un element i acabes de promoure’l per davant de cada enllaç, botó i camp de formulari de la pàgina. Afegeix-lo a deu elements i has renumerat manualment l’ordre de tabulació, cosa que vol dir que qualsevol edició futura (un nou ítem del menú, un camp de formulari canviat) trenca la seqüència silenciosament.

WCAG 2.4.3 (ordre de focus) exigeix que el focus es mogui en un ordre que preservi el significat i l’operabilitat. L’ordre visual hauria de coincidir amb l’ordre de tabulació. Un tabindex positiu gairebé sempre ho viola; els usuaris de teclat i de lector de pantalla aterren en llocs sorprenents, perden el context i abandonen el flux.

tabindex="-1" té el problema contrari quan es fa malament: elimina l’element de la seqüència de tabulació completament. Aplica-ho a un botó i els usuaris de teclat no hi podran arribar.

Com solucionar-ho

Regla pràctica: en codi d’aplicació normal, els únics valors acceptables són tabindex="0" i tabindex="-1", i només els necessites en elements no natius.

Quan tabindex="0" és correcte

Un widget interactiu personalitzat que el navegador no enfoca per defecte:

<!-- Pestanya personalitzada en un tablist -->
<div role="tab" tabindex="0" aria-selected="true">Resum</div>

<!-- Targeta que obre un modal en clicar -->
<div role="button" tabindex="0" onclick="openModal()">
  Veure detalls
</div>

Prefereix un <button> real sempre que puguis; et dona focus, activació amb Enter/Espai i semàntica de lector de pantalla gratis. Recorre a tabindex="0" només quan l’element natiu no et serveixi.

Quan tabindex="-1" és correcte

Un objectiu de focus programàtic, un element que enfocaràs via JavaScript però que no ha d’aparèixer a la seqüència de tabulació:

<!-- Resum d'errors, enfocat en enviar el formulari -->
<div id="errors" tabindex="-1" role="alert">
  3 camps necessiten atenció
</div>

<!-- Contenidor modal, enfocat en obrir -->
<div id="modal" tabindex="-1" role="dialog" aria-modal="true">
  ...
</div>

<!-- Objectiu de skip-link (contingut principal) -->
<main id="main" tabindex="-1">...</main>

Llavors a JS:

document.getElementById("errors").focus();

El -1 fa que .focus() funcioni sense inserir l’element a l’ordre de tabulació.

Quan un tabindex positiu és correcte

Gairebé mai. L’únic cas defensable és integrar-se amb un formulari heretat on no pots reordenar el DOM i l’ordre visual requereix reseqüenciació, i fins i tot llavors, arreglar el DOM és la resposta correcta.

Aplica-ho al CI

Captura-ho abans de la revisió:

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",
  },
}];

Per a repos d’HTML / templates en pla, axe-core capturarà el mateix problema en temps d’auditoria; consulta errors d’accessibilitat de Lighthouse.

Arreglar una pàgina que ja té tabindex positiu

  1. Treu cada atribut tabindex="1+".
  2. Recorre la pàgina amb el teclat. Anota qualsevol salt sorprenent.
  3. Reordena el DOM perquè la seqüència natural coincideixi amb la visual. Utilitza CSS (order, posicionament de grid) només per a reposicionament visual que no afecti l’ordre de lectura.
  4. Torna a executar l’auditoria.

Preguntes freqüents

Puc utilitzar tabindex="0" per fer que un <div> sigui clicable?

Pots fer-ho, però afegir role="button" i handlers d’Enter/Espai és la meitat de la feina; un <button> real és un sol element amb tot lligat. El patró tabindex="0" + role="button" es reserva per a casos on necessites un element no botó (per exemple, un treeitem, una pestanya) que l’especificació no inclou com a focusable nativament.

tabindex="-1" amaga l’element als lectors de pantalla?

No. tabindex="-1" només treu l’element de la navegació seqüencial per teclat. Els lectors de pantalla encara hi poden arribar via la seva pròpia navegació (encapçalaments, landmarks, cursor virtual). Per amagar-lo a la tecnologia assistiva, utilitza aria-hidden="true", però mai en un element interactiu.

I el roving tabindex dins d’un menú o grid?

Aquest és el patró correcte per a widgets compostos (menús, tablists, grids). Exactament un ítem té tabindex="0", la resta tenen tabindex="-1", i les fletxes mouen el focus alternant els valors. La comprovació ho té en compte: un sol 0 més molts -1 dins del mateix role="menu" / role="tablist" / role="grid" no es marca.

Fonts

Última actualització 2026-05-11