O teste de acessibilidade mais rápido que existe cabe em uma linha: feche o mouse, pressione Tab, e tente usar seu site. Se travar em algum ponto, você encontrou um bug que exclui usuários com deficiências motoras, pessoas cegas usando leitor de tela, e qualquer um que prefira o teclado ao mouse.
O que o WCAG exige
Dois critérios fundamentais, ambos de Nível A (o mínimo absoluto):
- 2.1.1 — Teclado: toda funcionalidade disponível por mouse deve estar disponível via teclado, sem exigir tempo específico para pressionar as teclas
- 2.4.7 — Foco Visível (AA): todo elemento interativo deve ter indicação visual de foco quando ativo
Uma falha em 2.1.1 é bloqueadora — o site não pode declarar nenhum nível WCAG enquanto tiver elementos inacessíveis por teclado.
O protocolo de teste em 5 minutos
Abra qualquer página do seu site e faça isso:
Passo 1 — Tab sequencial: Pressione Tab repetidamente e verifique:
- Você consegue ver onde está o foco? (Deve haver um indicador visual em volta do elemento ativo)
- A ordem de foco faz sentido? (Da esquerda para direita, de cima para baixo, seguindo a leitura visual)
- Todos os botões, links e campos de formulário são alcançáveis?
Passo 2 — Interações: Em cada elemento interativo, pressione Enter ou Espaço para ativá-lo. Em dropdowns e menus, verifique se Escape fecha e devolve o foco ao ponto de origem.
Passo 3 — Formulários: Preencha um formulário inteiro usando apenas o teclado. Envie com Enter. Funciona?
Passo 4 — Direção reversa: Shift+Tab navega na ordem inversa. Teste isso também.
Se o foco desapareceu, se você ficou preso em algum elemento, ou se alguma funcionalidade é inacessível — você encontrou uma falha real.
Os 5 problemas mais comuns
1. Foco invisível: o erro mais comum
/* ❌ O pior padrão — remove totalmente o indicador de foco */
* { outline: none; }
button:focus { outline: none; }
a:focus { outline: none; }
Esse padrão aparece em muitos resets CSS "modernos" porque o outline padrão do browser é visualmente feio. Mas a solução correta não é remover — é substituir por algo melhor:
/* ✅ Remove o padrão feio e substitui com foco acessível e bonito */
:focus-visible {
outline: 2px solid #0048B6;
outline-offset: 2px;
border-radius: 4px;
}
/* :focus-visible só aparece na navegação por teclado — não aparece ao clicar com o mouse */
Em Tailwind CSS:
<button class="focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-blue-600 focus-visible:ring-offset-2
rounded-lg px-4 py-2">
Enviar
</button>
A pseudo-classe :focus-visible (e focus-visible: no Tailwind) é inteligente: ela só mostra o anel de foco quando o elemento foi ativado por teclado, não por clique do mouse. Isso resolve tanto o problema de acessibilidade quanto a queixa estética.
2. Divs e spans clicáveis que não recebem foco
<!-- ❌ Div com onclick não é focável por teclado — usuário de teclado não consegue ativar -->
<div onclick="abrirModal()">Abrir detalhes</div>
<!-- ❌ Span também não -->
<span onclick="deletarItem(id)">Excluir</span>
<!-- ✅ Sempre use o elemento semântico correto -->
<button type="button" onclick="abrirModal()">Abrir detalhes</button>
<button type="button" onclick="deletarItem(id)">Excluir</button>
O <button> já tem tudo: focável por Tab, ativável por Enter e Espaço, role="button" implícito, e funciona com tecnologias assistivas sem nenhuma configuração extra.
Se por algum motivo você precisar de um elemento não-semântico interativo (ex: integrando uma biblioteca de terceiros), o mínimo necessário é:
<div
role="button"
tabindex="0"
onkeydown="if(event.key==='Enter'||event.key===' ')abrirModal()"
onclick="abrirModal()"
aria-label="Abrir detalhes do item"
>
Abrir detalhes
</div>
Mas prefira sempre o <button> — menos código, mais correto.
3. Armadilha de foco em modais e drawers
Quando um modal abre, o foco deve ir para dentro dele. Quando fecha, deve voltar ao elemento que o abriu. Sem esse comportamento, o usuário de teclado perde completamente a referência de onde está.
// React — padrão correto para modais acessíveis
import { useRef, useEffect } from 'react'
function Modal({
isOpen,
onClose,
triggerRef,
children,
}: {
isOpen: boolean
onClose: () => void
triggerRef: React.RefObject<HTMLButtonElement>
children: React.ReactNode
}) {
const modalRef = useRef<HTMLDivElement>(null)
// Move o foco para o modal ao abrir
useEffect(() => {
if (isOpen) {
modalRef.current?.focus()
}
}, [isOpen])
// Fecha com Escape e devolve o foco ao trigger
useEffect(() => {
if (!isOpen) return
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose()
triggerRef.current?.focus()
}
}
window.addEventListener('keydown', handler)
return () => window.removeEventListener('keydown', handler)
}, [isOpen, onClose, triggerRef])
if (!isOpen) return null
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
tabIndex={-1}
aria-labelledby="modal-title"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50
focus-visible:outline-none"
>
<div className="bg-white rounded-2xl p-6 max-w-md w-full shadow-xl">
<h2 id="modal-title" className="font-bold text-lg text-slate-900 mb-4">
Título do Modal
</h2>
{children}
<button
type="button"
onClick={() => { onClose(); triggerRef.current?.focus() }}
className="mt-4 text-sm text-gray-500 hover:text-gray-900
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-blue-600 rounded"
>
Fechar
</button>
</div>
</div>
)
}
O tabIndex={-1} no container do modal permite mover o foco programaticamente via .focus() sem fazer o elemento aparecer na sequência de Tab do usuário.
4. Skip link ausente
Em páginas com menus de navegação longos, usuários de teclado precisam pressionar Tab dezenas de vezes para chegar ao conteúdo principal. O skip link resolve isso:
<!-- Primeiro elemento do <body>, visualmente oculto mas presente no DOM -->
<a
href="#main-content"
class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4
focus:z-50 focus:px-4 focus:py-2 focus:bg-blue-600 focus:text-white
focus:rounded-lg focus:font-semibold focus:shadow-lg"
>
Ir para o conteúdo principal
</a>
<!-- ... header e navegação ... -->
<main id="main-content" tabindex="-1">
<!-- O tabIndex="-1" permite receber foco programático -->
</main>
Ao pressionar Tab na primeira vez na página, o link "Ir para o conteúdo principal" aparece. Ao pressionar Enter, o foco pula direto para o <main>.
5. Ordem de foco quebrada por CSS
Layouts flexbox e grid podem reordenar elementos visualmente sem mudar o DOM, criando uma sequência de Tab que não corresponde ao que o usuário vê:
<!-- ❌ CSS reordena visualmente mas Tab segue o DOM — sequência confusa -->
<div style="display:flex; flex-direction:row-reverse">
<button>Cancelar</button> <!-- Tab chega aqui por último visualmente -->
<button>Confirmar</button> <!-- Tab chega aqui primeiro visualmente -->
</div>
<!-- ✅ Ordem do DOM corresponde à ordem visual -->
<div style="display:flex; flex-direction:row">
<button>Confirmar</button>
<button>Cancelar</button>
</div>
Nunca use tabindex com valor positivo (como tabindex="2", tabindex="5") para tentar corrigir a ordem. Isso cria uma sequência de foco global imprevisível e difícil de manter. Reorganize o HTML.
Ferramentas para automatizar a detecção
O teste manual com Tab é insubstituível, mas ferramentas ajudam a identificar problemas antes:
- Web para Todos — Analisador: detecta automaticamente foco ausente,
outline: nonesem substituto, e elementos interativos não-focáveis - axe DevTools (extensão Chrome/Firefox): audita a página aberta no browser com 100+ regras
- Lighthouse: aba Accessibility no Chrome DevTools — gratuito, integrado ao browser
- NVDA + Firefox (Windows): teste real com leitor de tela — gratuito
Resultado
Um site navegável por teclado não é só para pessoas com deficiência. Formulários preenchíveis por Tab, modais que fecham com Escape, e foco sempre visível são qualidade básica de produto — e fazem qualquer usuário mais rápido.
O critério mínimo é objetivo: se você consegue usar o site inteiro sem tocar no mouse, sem ficar preso em nenhum elemento, o 2.1.1 passa.
Critérios WCAG: 2.1.1 Teclado — Nível A | 2.4.7 Foco Visível — Nível AA | 2.4.3 Ordem do Foco — Nível A
Verifique seu site agora: Analisador de acessibilidade gratuito do Web para Todos