Acessibilidade com Claude Code: HTML semântico, ARIA, axe e revisão manual
Fluxo prático para melhorar acessibilidade com Claude Code: HTML, teclado, formulários, foco, axe e leitor de tela.
Acessibilidade não é rodar uma ferramenta no fim do release. Ela começa com HTML semântico, passa por navegação por teclado, gerenciamento de foco, erros de formulário, contraste de cor e termina com testes automatizados e verificação manual com leitor de tela.
Claude Code acelera esse fluxo, mas somente quando o pedido é específico. Se você escrever “deixe acessível”, ele pode adicionar ARIA em um div sem implementar Enter ou Space, criar um modal bonito que deixa o foco escapar para o fundo ou exibir erros que o leitor de tela não anuncia.
As referências aqui são oficiais: W3C WCAG 2.2 como alvo prático, WAI-ARIA Authoring Practices Guide para padrões de widgets, MDN ARIA para a regra de HTML semântico primeiro, documentação axe-core da Deque para automação e documentação oficial do Claude Code para a ferramenta.
Defina o alvo antes do patch
“Melhorar acessibilidade” é amplo demais. Um alvo bom para Claude Code é: WCAG 2.2 AA como referência, HTML semântico antes de ARIA, fluxo principal operável por teclado, foco visível, erros compreensíveis e evidência com teste automático e revisão manual.
| Área | Linha mínima | Falha comum |
|---|---|---|
| HTML semântico | Usar button, a, form, label, main, nav corretamente | div clicável substitui controle nativo |
| Teclado | Tab, Shift+Tab, Enter, Space e Escape cobrem o fluxo | Modal fecha só com mouse |
| Foco | Foco entra na UI nova e volta ao gatilho ao fechar | Foco some atrás do diálogo |
| ARIA | Usar apenas quando HTML não expressa o estado | aria-label mascara falta de label visível |
| Cor | Texto, controles, erros e foco têm contraste | Erro depende só da cor vermelha |
| Formulários | Labels, ajuda, estado inválido e erros ligados ao input | Erro aparece, mas não é anunciado |
| Testes | axe mais teclado e leitor de tela | Zero violações automáticas vira falsa aprovação total |
MDN recomenda usar elementos HTML nativos quando eles já têm a semântica e o comportamento necessários. Portanto, peça ao Claude Code para corrigir a estrutura antes de adicionar estados ARIA como aria-invalid, aria-expanded ou aria-modal.
Prompt seguro para Claude Code
Um prompt bom parece uma checklist de revisão. Ele define escopo, protege texto comercial e pede prova.
claude <<'PROMPT'
Scope:
- Review only src/components/CheckoutForm.tsx and its tests.
- Do not change pricing copy, analytics events, or unrelated styles.
Accessibility target:
- Use WCAG 2.2 AA as the practical target.
- Prefer semantic HTML before ARIA.
- Add ARIA only when native HTML cannot express the state.
Check these items:
- Labels, descriptions, required state, and validation errors.
- Keyboard operation with Tab, Shift+Tab, Enter, Space, and Escape.
- Focus order, visible focus, and focus return after closing UI.
- Color contrast and non-color error indicators.
- Automated axe check plus manual screen-reader notes.
Output:
- Findings first, with file and line references.
- Minimal patch.
- Commands to verify.
- Any remaining risk.
PROMPT
Findings first evita patch sem diagnóstico. Claude Code aponta o problema, depois gera a menor mudança possível. Isso protege páginas com preço, CTA, Gumroad e analytics. O mesmo hábito de escopo pequeno aparece em dicas de produtividade com Claude Code.
Caso 1: CTA de produto ou artigo
CTA é caminho de receita. Se a ação é navegação, use link real em vez de cartão inteiro com onclick.
<div class="hero-card" onclick="location.href='/en/products'">
<div class="title">Claude Code Templates</div>
<div class="button">Buy now</div>
</div>
Uma base melhor separa título, descrição e destino.
<section aria-labelledby="templates-heading" class="product-cta">
<h2 id="templates-heading">Reduza revisões com templates Claude Code</h2>
<p>
Copie prompts reutilizáveis para implementação, revisão,
debugging e documentação.
</p>
<a class="primary-link" href="/en/products">
Ver recursos de produto
</a>
</section>
Ao pedir a mudança, diga para preservar URLs, atributos de tracking e texto de conversão. Um patch acessível que quebra o funil não é bom para produção.
Caso 2: Formulário de contato ou consultoria
Formulários conectam acessibilidade e conversão. Se o usuário não entende o campo, o formato esperado ou o erro, ele não envia a solicitação.
import { FormEvent, useState } from "react";
type Errors = {
name?: string;
email?: string;
};
export function ConsultationForm() {
const [errors, setErrors] = useState<Errors>({});
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
const nextErrors: Errors = {};
if (!String(data.get("name") || "").trim()) {
nextErrors.name = "Informe seu nome.";
}
if (!String(data.get("email") || "").includes("@")) {
nextErrors.email = "Informe um e-mail válido.";
}
setErrors(nextErrors);
}
return (
<form aria-labelledby="consultation-title" onSubmit={handleSubmit} noValidate>
<h2 id="consultation-title">Solicitação de consultoria</h2>
<div className="field">
<label htmlFor="name">Nome</label>
<input
id="name"
name="name"
autoComplete="name"
aria-invalid={errors.name ? "true" : "false"}
aria-describedby={errors.name ? "name-error" : undefined}
/>
{errors.name && (
<p id="name-error" role="alert">
{errors.name}
</p>
)}
</div>
<div className="field">
<label htmlFor="email">E-mail</label>
<p id="email-help">Use um endereço em que possamos responder.</p>
<input
id="email"
name="email"
type="email"
autoComplete="email"
aria-invalid={errors.email ? "true" : "false"}
aria-describedby={
errors.email ? "email-help email-error" : "email-help"
}
/>
{errors.email && (
<p id="email-error" role="alert">
{errors.email}
</p>
)}
</div>
<button type="submit">Enviar solicitação</button>
</form>
);
}
A falha comum é exibir erro sem conectar ao input. Outra é referenciar sempre um ID de erro que ainda não existe. Ajuda fixa fica sempre ligada; erro só quando renderizado.
Caso 3: Modal, command palette e menu
Modal não é só overlay. O padrão Dialog Modal do WAI-ARIA exige foco dentro, Tab circulando internamente, Escape para fechar e retorno ao botão que abriu.
import { ReactNode, useEffect, useRef } from "react";
type ModalProps = {
open: boolean;
title: string;
onClose: () => void;
children: ReactNode;
};
const focusableSelector = [
"a[href]",
"button:not([disabled])",
"input:not([disabled])",
"select:not([disabled])",
"textarea:not([disabled])",
'[tabindex]:not([tabindex="-1"])',
].join(",");
export function AccessibleModal(props: ModalProps) {
const { open, title, onClose, children } = props;
const dialogRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!open) return;
previousFocusRef.current = document.activeElement as HTMLElement;
const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(
focusableSelector
);
focusable?.[0]?.focus();
function onKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") onClose();
if (event.key !== "Tab" || !dialogRef.current) return;
const items = [...dialogRef.current.querySelectorAll<HTMLElement>(
focusableSelector
)];
const first = items[0];
const last = items[items.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last?.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first?.focus();
}
}
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
previousFocusRef.current?.focus();
};
}, [open, onClose]);
if (!open) return null;
return (
<div className="modal-backdrop">
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="modal-panel"
>
<h2 id="modal-title" tabIndex={-1}>
{title}
</h2>
{children}
<button type="button" onClick={onClose}>
Fechar
</button>
</div>
</div>
);
}
Para dropdowns, confirme se o padrão ARIA é mesmo necessário. O Menu Button Pattern serve para menus de comando. Navegação comum costuma funcionar melhor com nav e links.
Cor, foco e mobile
Regressões aparecem quando o design remove outline, usa cinza claro ou mostra erro só pela cor. Mantenha foco visível e indicador não baseado em cor.
.primary-link {
background: #0f766e;
border-radius: 6px;
color: #ffffff;
display: inline-flex;
font-weight: 700;
min-height: 44px;
padding: 0.75rem 1rem;
}
.primary-link:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
.field [role="alert"] {
border-left: 4px solid #b91c1c;
color: #7f1d1d;
margin-top: 0.5rem;
padding-left: 0.75rem;
}
Cheque também mobile: botão de fechar pequeno, foco escondido sob header fixo e CTA estreito são falhas comuns.
axe e revisão manual
axe encontra problemas estruturais rapidamente, mas não prova que o texto faz sentido ou que a ordem de leitura ajuda no fluxo real.
npm install -D @axe-core/playwright @playwright/test
npx playwright install --with-deps chromium
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
test("consultation form has no serious accessibility issues", async ({ page }) => {
await page.goto("/contact");
const results = await new AxeBuilder({ page })
.include("main")
.withTags(["wcag2a", "wcag2aa", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
Na revisão manual, comece por CTA de produto, formulário, checkout, modal, navegação e recuperação de erro. NVDA no Windows e VoiceOver no macOS são pontos de partida realistas.
Falhas concretas para procurar
role="button"sem Enter e Space.- Botão só com ícone e sem nome acessível.
alt="image"ou texto alternativo inútil.aria-hidden="true"escondendo modal ou live region.- Erro visível sem
aria-describedby. - Foco removido sem substituto.
- Modal fecha sem devolver foco ao gatilho.
- Teste automático cobre só página marketing, não o formulário real.
Use essa lista como prompt de revisão depois do patch.
CTA e resultado verificado
Para transformar isso em rotina, veja os produtos Claude Code. Para prompts repetíveis de review e debugging, use os 50 templates de prompts Claude Code. Para equipe com permissões, hooks e recibos de verificação, consultoria é o próximo passo.
Nesta atualização, testei três falhas reais: CTA em div clicável, formulário com erro não anunciado e modal sem retorno de foco. O melhor fluxo foi pedir findings primeiro, patch mínimo depois e checar teclado mais VoiceOver. axe ajudou na estrutura, mas a clareza do fluxo exigiu revisão humana.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.