accessibility

Uso correcto de tabindex

Detecta valores positivos de tabindex y el abuso de tabindex="-1". Los positivos rompen el orden del DOM; solo 0 y -1 tienen sitio en código de producción.

Qué comprueba esta verificación

Escanea todos los elementos con atributo tabindex y reporta dos problemas:

  1. Valores positivos (tabindex="1", "2", etc.) — anulan el orden natural del DOM al tabular y casi siempre provocan bugs de foco.
  2. Abuso de tabindex="-1" en elementos que deberían ser alcanzables por teclado — un error frecuente en botones y enlaces personalizados.

La comprobación también señala cuando se aplica tabindex a elementos que ya son enfocables de forma nativa (<a href>, <button>, <input>), donde suele ser redundante.

Por qué importa

Un tabindex positivo crea un universo paralelo de teclado. El navegador recorre primero todos los valores positivos — en orden numérico — y luego vuelve al orden del DOM para el resto. Añade tabindex="1" a un elemento y lo acabas de promover por delante de cada enlace, botón y campo de formulario de la página. Añádelo a diez elementos y has renumerado el orden de tabulación a mano, lo que significa que cada edición futura (un nuevo elemento de menú, un campo cambiado de sitio) romperá la secuencia en silencio.

WCAG 2.4.3 (Orden del foco) exige que el foco se mueva en un orden que preserve el significado y la operabilidad. El orden visual debería coincidir con el orden de tabulación. Un tabindex positivo casi siempre lo incumple — quienes usan teclado y lectores de pantalla aterrizan en sitios inesperados, pierden el contexto y abandonan el flujo.

tabindex="-1" tiene el problema opuesto cuando se usa mal: saca al elemento por completo de la secuencia de tabulación. Aplícalo a un botón y los usuarios de teclado no podrán alcanzarlo.

Cómo arreglarlo

Regla práctica: en código de aplicación normal, los únicos valores aceptables son tabindex="0" y tabindex="-1", y solo los necesitas en elementos no nativos.

Cuándo tabindex="0" es lo correcto

Un widget interactivo personalizado al que el navegador no enfoca por defecto:

<!-- Custom tab in a tablist -->
<div role="tab" tabindex="0" aria-selected="true">Overview</div>

<!-- Card that opens a modal on click -->
<div role="button" tabindex="0" onclick="openModal()">
  View details
</div>

Prefiere un <button> real siempre que puedas — te da foco, activación con Enter/Espacio y semántica de lector de pantalla gratis. Tira de tabindex="0" solo cuando el elemento nativo no encaje.

Cuándo tabindex="-1" es lo correcto

Un objetivo de foco programático — un elemento que enfocarás vía JavaScript pero que no debería aparecer en la secuencia de tabulación:

<!-- Error summary, focused on form submit -->
<div id="errors" tabindex="-1" role="alert">
  3 fields need attention
</div>

<!-- Modal container, focused on open -->
<div id="modal" tabindex="-1" role="dialog" aria-modal="true">
  ...
</div>

<!-- Skip-link target (main content) -->
<main id="main" tabindex="-1">...</main>

Y en JS:

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

El -1 hace que .focus() funcione sin insertar el elemento en el orden de tabulación.

Cuándo es correcto un tabindex positivo

Casi nunca. El único caso defendible es integrar con un formulario heredado donde no puedes reordenar el DOM y el orden visual exige reorganizarlo — e incluso ahí, arreglar el DOM es la respuesta correcta.

Línea de defensa en CI

Cógelo antes de la revisión:

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

Para repos de HTML / plantillas planas, axe-core detectará el mismo problema en tiempo de auditoría — ver errores de accesibilidad de Lighthouse.

Cómo arreglar una página que ya tiene tabindex positivo

  1. Elimina cada atributo tabindex="1+".
  2. Tabula por la página con el teclado. Anota cualquier salto sorprendente.
  3. Reordena el DOM para que la secuencia natural coincida con la visual. Usa CSS (order, colocación en grid) solo para reposicionar visualmente sin afectar al orden de lectura.
  4. Vuelve a ejecutar la auditoría.

Preguntas frecuentes

¿Puedo usar tabindex="0" para hacer un <div> clicable?

Puedes, pero añadir role="button" y manejadores de Enter/Espacio es la mitad del trabajo — un <button> real es un solo elemento con todo cableado. El patrón tabindex="0" + role="button" se reserva para casos en los que necesitas un elemento no-botón (por ejemplo, un treeitem o una pestaña) que la spec no incluye como enfocable de forma nativa.

¿tabindex="-1" oculta el elemento a los lectores de pantalla?

No. tabindex="-1" solo retira el elemento de la navegación secuencial por teclado. Los lectores de pantalla pueden seguir alcanzándolo con su propia navegación (encabezados, landmarks, cursor virtual). Para ocultarlo a la tecnología de apoyo, usa aria-hidden="true" — pero nunca sobre un elemento interactivo.

¿Y el “roving tabindex” dentro de un menú o un grid?

Ese es el patrón correcto para widgets compuestos (menús, tablists, grids). Exactamente un ítem tiene tabindex="0", el resto tabindex="-1", y las flechas mueven el foco alternando los valores. La comprobación lo tiene en cuenta: un único 0 con muchos -1 dentro del mismo role="menu" / role="tablist" / role="grid" no se marca.

Fuentes

Última actualización 2026-05-11