
Acessibilidade web (a11y) na prática: WCAG, ARIA e testes que funcionam
Acessibilidade não é um extra para 'quando sobrar tempo': é requisito de qualidade de software — e, em muitos contextos, requisito legal. No Brasil, a Lei nº 13.146/2015 (Estatuto da Pessoa com Deficiência) e a Lei nº 14.133/2021 para compras públicas exigem acessibilidade em sites, e o modelo-ABNT NBR 17225:2025 passou a normatizar o tema alinhado ao WCAG. Este artigo é o essencial técnico que todo front-end precisa dominar.
WCAG em cinco minutos
As Web Content Accessibility Guidelines organizam-se em quatro princípios (POUR): Perceptível, Operável, Compreensível e Robusto. Os níveis de conformidade são A (mínimo), AA (alvo padrão de mercado e da maioria das legislações) e AAA (aspiracional). Na prática, seu objetivo é WCAG 2.2 nível AA.
| Requisito (WCAG 2.2 AA) | Critério | Como verificar |
|---|---|---|
| Contraste de texto normal | Mínimo 4,5:1 | DevTools → painel de contraste ou WebAIM Contrast Checker |
| Contraste de texto grande (18pt+) | Mínimo 3:1 | Idem |
| Foco visível | Indicador de foco nunca removido | Tab pela página inteira |
| Alvo de toque | Mínimo 24×24 CSS px (2.2) | Inspeção de tamanho de botões/links |
| Texto alternativo | alt em toda imagem informativa | Revisão + axe |
| Redimensionar texto | Layout funciona com zoom de 200% | Zoom do browser |
HTML semântico: 80% do trabalho
A regra número um de a11y: use o elemento nativo para o propósito nativo. Um <button> já vem com foco, ativação por Enter e Espaço, e papel correto na árvore de acessibilidade. Um <div onclick> não vem com nada disso — e você terá que reimplementar cada comportamento manualmente (e provavelmente errar).
<!-- Ruim: inacessível em todos os sentidos -->
<div class="btn" onclick="salvar()">Salvar</div>
<!-- Bom: foco, teclado, papel e nome acessível de graça -->
<button type="button" onclick="salvar()">Salvar</button>
<!-- Estrutura que leitores de tela usam para navegar -->
<header>...</header>
<nav aria-label="Principal">...</nav>
<main>
<h1>Título único da página</h1>
<article>...</article>
</main>
<footer>...</footer>Landmarks (header, nav, main, footer) permitem que usuários de leitor de tela pulem direto para a região desejada. Hierarquia de headings (h1 → h2 → h3, sem pular níveis) funciona como sumário navegável. Links devem fazer sentido fora de contexto: 'Leia a documentação de acessibilidade' em vez de 'clique aqui'.
ARIA: a primeira regra é não usar
A primeira regra do ARIA, da própria especificação: se existe elemento HTML nativo com a semântica desejada, use-o em vez de adicionar role/aria-*. ARIA não adiciona comportamento — apenas descreve. Um role='button' numa div não a torna clicável por teclado; você ainda precisará implementar keydown, foco e estados. Onde ARIA é legítimo e necessário:
- aria-label / aria-labelledby: nome acessível para ícones-only, ex. <button aria-label='Fechar'>×</button>.
- aria-live: anuncia mudanças dinâmicas (toasts, contadores) sem mover o foco.
- aria-expanded / aria-controls: estados de acordeões e menus customizados.
- aria-invalid + aria-describedby: vincula erro de formulário ao campo.
- aria-hidden='true': esconde conteúdo puramente decorativo de leitores de tela.
<!-- Campo com erro anunciado corretamente -->
<label for="email">E-mail</label>
<input id="email" name="email" type="email"
aria-invalid="true" aria-describedby="email-erro" />
<p id="email-erro" role="alert">Informe um e-mail válido.</p>
<!-- Notificação dinâmica anunciada sem roubar foco -->
<div aria-live="polite" id="toast"></div>Navegação por teclado e gestão de foco
Todo elemento interativo precisa ser alcançável e operável por teclado. Dois padrões merecem atenção: modais (o foco deve entrar no modal ao abrir, ficar preso dentro dele — focus trap — e voltar ao elemento que o abriu ao fechar) e o link 'Pular para o conteúdo' como primeiro item focável da página.
/* NUNCA faça: outline: none; sem substituto */
/* Foque com estilo claro apenas para teclado */
:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
/* Skip link: invisível até receber foco */
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
position: static;
left: auto;
}Testes: automatizado cobre ~30%, o resto é manual
Ferramentas como axe detectam problemas objetivos (contraste, alt ausente, label faltando), mas não julgam qualidade do alt, ordem lógica de leitura ou usabilidade real com leitor de tela. O fluxo maduro combina as duas frentes:
// Teste automatizado com axe-core + Playwright
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test("página inicial sem violações críticas de a11y", async ({ page }) => {
await page.goto("/");
const resultado = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.analyze();
const criticas = resultado.violations.filter(
(v) => v.impact === "critical" || v.impact === "serious"
);
expect(criticas).toEqual([]);
});- Automatizado: axe-core no CI, Lighthouse (score de acessibilidade), eslint-plugin-jsx-a11y no lint.
- Manual: navegue a página inteira só com Tab/Shift+Tab/Enter/Espaço.
- Leitor de tela: NVDA (Windows, gratuito) ou VoiceOver (macOS/iOS) — 15 minutos de uso revelam problemas que nenhum scanner vê.
- Zoom: teste 200% e 400% de zoom e prefers-reduced-motion para animações.
Overlays de 'acessibilidade instantânea' (widgets que prometem conformidade com uma linha de JavaScript) não tornam um site conforme WCAG e são criticados por especialistas e por usuários com deficiência. Não há atalho que substitua markup correto.
Conclusão
Acessibilidade é, na maior parte, fazer o básico de HTML com seriedade: elementos nativos, landmarks, labels, contraste AA, foco visível e teste com teclado. ARIA entra pontualmente onde o HTML não alcança. Automatize o que é detectável em CI e reserve tempo para teste manual com leitor de tela — é a combinação que entrega um produto realmente utilizável por todas as pessoas.
Perguntas frequentes
+Qual nível de WCAG devo cumprir?
WCAG 2.2 nível AA é o alvo padrão de mercado e o referenciado pela maioria das legislações e editais, incluindo o modelo brasileiro NBR 17225. AAA é aspiracional e raramente exigido por inteiro.
+Imagens decorativas precisam de alt?
Sim, mas vazio: alt="". Isso instrui o leitor de tela a ignorá-la. Omitir o atributo alt faz alguns leitores anunciarem o nome do arquivo — pior que a ausência.
+Teste automatizado garante conformidade?
Não. Estudos do próprio projeto axe indicam que ferramentas automáticas detectam algo entre 30% e 40% dos problemas. Elas são excelentes para regressão em CI, mas avaliação manual com teclado e leitor de tela continua indispensável.
+Divs com role='button' são aceitáveis?
Funcionam se você reimplementar todo o comportamento nativo (tabindex, keydown de Enter e Espaço, estados aria-pressed/disabled), mas quase sempre é menos código — e menos bugs — usar <button> diretamente.
Fontes consultadas
- W3C — WCAG 2.2
- W3C — Using ARIA (primeira regra do ARIA)
- MDN — Accessibility
- axe-core — motor de testes de acessibilidade
Revisão editorial: publicado em . Última revisão em . Conteúdo educativo, sem patrocínio das ferramentas citadas.
Leia também

Serverless: quando funções serverless valem a pena (e quando viram armadilha)
AWS Lambda, Cloudflare Workers e Vercel Functions comparados: modelo de custo, cold starts, limites de execução e os workloads onde serverless brilha ou falha.

Core Web Vitals na prática: LCP, INP e CLS medidos e corrigidos
Guia técnico dos três Core Web Vitals: como medir com dados reais (CrUX/RUM), as causas mais comuns de LCP, INP e CLS ruins e as correções que funcionam.

PostgreSQL além do básico: índices, EXPLAIN e JSONB na prática
Aprenda a ler planos de execução com EXPLAIN ANALYZE, escolher entre B-tree, GIN e BRIN, e usar JSONB sem destruir a performance das suas queries.