accessibility

Boas práticas de tabindex

Assinala valores positivos de tabindex e abuso de tabindex="-1". Valores positivos quebram a ordem do DOM; só 0 e -1 pertencem a markup de produção.

O que esta verificação faz

Analisa todos os elementos com atributo tabindex e reporta dois problemas:

  1. Valores positivos (tabindex="1", "2", etc.) — sobrepõem-se à ordem natural do DOM e quase sempre causam bugs de foco.
  2. Abuso de tabindex="-1" em elementos que deviam ser alcançáveis por teclado — erro comum em botões e links personalizados.

A verificação também nota quando tabindex é aplicado a elementos que já são focáveis nativamente (<a href>, <button>, <input>), onde costuma ser redundante.

Porque é importante

Um tabindex positivo cria um universo paralelo de teclado. O navegador percorre primeiro todos os valores positivos — por ordem numérica — e depois cai na ordem do DOM para tudo o resto. Adiciona tabindex="1" a um elemento e acabaste de o promover à frente de cada link, botão e campo de formulário da página. Adiciona-o a dez elementos e renumeraste manualmente a ordem de tab, o que significa que cada edição futura (um novo item de menu, um campo de formulário trocado) quebra silenciosamente a sequência.

A WCAG 2.4.3 (Focus Order) exige que o foco se mova numa ordem que preserve o significado e a operabilidade. A ordem visual deve corresponder à ordem de tab. Tabindex positivo quase sempre viola isto — utilizadores de teclado e de leitor de ecrã aterram em sítios surpreendentes, perdem contexto e abandonam o fluxo.

O tabindex="-1" tem o problema oposto quando mal usado: remove o elemento da sequência de tab por completo. Aplica-o a um botão e os utilizadores de teclado não conseguem chegar lá.

Como corrigir

Regra prática: em código de aplicação normal, os únicos valores aceitáveis são tabindex="0" e tabindex="-1", e só precisas deles em elementos não nativos.

Quando tabindex="0" está certo

Um widget interativo personalizado que o navegador não foca por defeito:

<!-- Tab personalizado num tablist -->
<div role="tab" tabindex="0" aria-selected="true">Visão geral</div>

<!-- Card que abre um modal ao clicar -->
<div role="button" tabindex="0" onclick="openModal()">
  Ver detalhes
</div>

Prefere um <button> real sempre que possas — ganhas foco, ativação por Enter/Espaço e semântica para leitor de ecrã de graça. Usa tabindex="0" apenas quando o elemento nativo não serve.

Quando tabindex="-1" está certo

Um alvo de foco programático — um elemento que vais focar via JavaScript mas que não deve aparecer na sequência de tab:

<!-- Resumo de erros, focado ao submeter o formulário -->
<div id="errors" tabindex="-1" role="alert">
  3 campos precisam de atenção
</div>

<!-- Container de modal, focado ao abrir -->
<div id="modal" tabindex="-1" role="dialog" aria-modal="true">
  ...
</div>

<!-- Alvo de skip-link (conteúdo principal) -->
<main id="main" tabindex="-1">...</main>

Depois em JS:

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

O -1 faz com que .focus() funcione sem inserir o elemento na ordem de Tab.

Quando tabindex positivo está certo

Quase nunca. O único caso defensável é integração com um formulário legacy onde não podes reorganizar o DOM e a ordem visual exige re-sequenciamento — e mesmo aí, corrigir o DOM é a resposta certa.

Lint na CI

Apanha isto antes da revisão:

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 repositórios de HTML / templates vanilla, o axe-core apanha o mesmo problema na auditoria — vê falhas de acessibilidade no Lighthouse.

Corrigir uma página que já tem tabindex positivo

  1. Remove todos os atributos tabindex="1+".
  2. Percorre a página com o teclado. Anota saltos surpreendentes.
  3. Reorganiza o DOM para que a sequência natural corresponda à visual. Usa CSS (order, posicionamento em grid) apenas para reposicionamento visual que não afete a ordem de leitura.
  4. Volta a correr a auditoria.

Perguntas frequentes

Posso usar tabindex="0" para tornar um <div> clicável?

Podes, mas adicionar role="button" e handlers de Enter/Espaço é metade do trabalho — um <button> real é um elemento com tudo ligado. O padrão tabindex="0" + role="button" está reservado para casos em que precisas de um elemento não-botão (ex. um treeitem, um tab) que a spec não inclui como focável nativamente.

tabindex="-1" esconde o elemento dos leitores de ecrã?

Não. O tabindex="-1" apenas remove o elemento da navegação sequencial por teclado. Os leitores de ecrã ainda lá chegam pela sua própria navegação (cabeçalhos, landmarks, cursor virtual). Para esconder de tecnologia de apoio, usa aria-hidden="true" — mas nunca num elemento interativo.

E o tabindex rotativo dentro de um menu ou grid?

Esse é o padrão certo para widgets compostos (menus, tablists, grids). Exatamente um item tem tabindex="0", os restantes têm tabindex="-1", e as teclas de seta movem o foco trocando os valores. A verificação contempla isto: um único 0 mais muitos -1 dentro do mesmo role="menu" / role="tablist" / role="grid" não é assinalado.

Fontes

Última atualização 2026-05-11