WCAG 2.1 AA no mundo real
Guia prático com exemplos de código reais extraídos deste projeto: ARIA, foco, semântica HTML, contraste e um checklist para usar em todos os PRs.
Este guia foca em boas práticas de desenvolvimento. Para auditoria profissional de acessibilidade, consulte um especialista certificado em WCAG.
ARIA (Accessible Rich Internet Applications) completa o HTML onde a semântica nativa não é suficiente. Use apenas quando necessário — HTML semântico é sempre preferível.
Roles Comuns
| role="dialog" | Modais e dialogs | Combine com aria-modal="true" e focus trap |
| role="alert" | Mensagens de erro urgentes | Lido automaticamente por screen readers |
| role="status" | Atualizações não urgentes | Menos intrusivo que alert |
| role="progressbar" | Barras de progresso | Adicione aria-valuenow, aria-valuemin e aria-valuemax |
| role="tablist" | Grupos de abas | Combine com role="tab" e role="tabpanel" |
❌ Ruim — sem semântica
<!-- ❌ Sem contexto — screen reader lê "botão" sem saber o que faz --><button><X className="h-4 w-4" /></button><!-- ❌ Placeholder não é acessível como label --><input type="email" placeholder="Email" />
✅ Bom — semântico
<!-- ✅ Botões com contexto semântico claro --><button aria-label="Fechar diálogo de configurações"><X className="h-4 w-4" /></button><button aria-label="Editar perfil de João Silva"><Pencil className="h-4 w-4" /></button><!-- ✅ aria-describedby para detalhes adicionais --><inputid="email"type="email"aria-describedby="email-hint"/><p id="email-hint" className="text-sm text-muted-foreground">Use seu email corporativo</p>
Live Regions
Para conteúdo que atualiza dinamicamente sem reload de página:
aria-live="polite"Lê quando o usuário para de digitar. Ideal para notificações.
aria-live="assertive"Lê imediatamente. Apenas para erros críticos.
aria-live="off"Desabilita atualizações dinâmicas (padrão).
Usuários de teclado dependem do foco visível e previsível. Focus traps, skip links e retorno ao elemento de origem são essenciais.
Skip Link
O primeiro elemento focável da página — permite pular para o conteúdo principal sem navegar por toda a navegação.
// src/components/skip-link.tsxexport function SkipLink() {return (<ahref="#main-content"className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4focus:z-50 focus:rounded-lg focus:bg-primary focus:px-4focus:py-2 focus:text-sm focus:font-medium focus:text-primary-foreground">Pular para o conteúdo principal</a>);}// Em layout.tsx<SkipLink /><header>...</header><main id="main-content">...</main>
Focus Trap em Modais
Enquanto um modal está aberto, Tab e Shift+Tab devem circular apenas dentro do modal.
// Focus trap simples para modaisfunction trapFocus(container: HTMLElement) {const focusable = container.querySelectorAll<HTMLElement>('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');const first = focusable[0];const last = focusable[focusable.length - 1];container.addEventListener("keydown", (e) => {if (e.key !== "Tab") return;if (e.shiftKey) {if (document.activeElement === first) {e.preventDefault();last.focus();}} else {if (document.activeElement === last) {e.preventDefault();first.focus();}}});}
Foco Visível
Nunca remova o outline de foco sem fornecer um substituto visível. Este projeto usa Tailwind focus-visible.
O HTML semântico correto é a base da acessibilidade. Screen readers usam landmarks para navegação rápida.
Landmarks e Hierarquia
Cada landmark cria um ponto de navegação para screen readers.
| <header> | banner | Cabeçalho da página (logo, nav principal) |
| <nav> | navigation | Navegação principal ou secundária |
| <main> | main | Conteúdo principal — apenas 1 por página |
| <section> | region | Seção com aria-labelledby |
| <aside> | complementary | Conteúdo relacionado mas secundário |
| <footer> | contentinfo | Rodapé da página |
❌ Ruim — sem semântica
<!-- ❌ Div soup — nenhuma informação semântica --><div class="header"><div class="nav"><div class="nav-item">Home</div></div></div><div class="main"><div class="title">Título</div><div class="content">...</div></div>
✅ Bom — semântico
<!-- ✅ HTML semântico — screen readers entendem a estrutura --><header><nav aria-label="Navegação principal"><ul><li><a href="/">Home</a></li></ul></nav></header><main id="main-content"><article><h1>Título da Página</h1><section aria-labelledby="section-title"><h2 id="section-title">Seção</h2><p>Conteúdo...</p></section></article></main><footer><p>© 2025 Dev Showcase</p></footer>
Live Region — Anúncios Dinâmicos
// ✅ Anúncio de estado para screen readersfunction SearchResults({ count }: { count: number }) {return (<>{/* Visualmente escondido mas lido por screen readers */}<divrole="status"aria-live="polite"aria-atomic="true"className="sr-only">{count === 0? "Nenhum resultado encontrado": `${count} resultados encontrados`}</div>{/* Conteúdo visual */}<ul>{/* ... */}</ul></>);}
WCAG 2.1 AA exige contraste mínimo de 4.5:1 para texto normal e 3:1 para texto grande (18pt ou 14pt negrito).
Requisitos WCAG 2.1 AA
| Texto normal (< 18pt) | 4.5:1 | AA |
| Texto grande (≥ 18pt ou 14pt bold) | 3:1 | AA |
| Componentes UI e gráficos | 3:1 | AA |
| Texto normal | 7:1 | AAA |
Revise estes itens antes de abrir um Pull Request com qualquer mudança de interface.
1 em cada 6 pessoas no mundo tem alguma deficiência. Código acessível não é opcional — é responsabilidade profissional.