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
Escada de segurança de permissões no Claude Code
Amplie de read-only para edições limitadas, comandos de prova e deploy checks sem perder controle.
Claude Code Small PR Proof Pack: pequenas mudanças fáceis de revisar
Um pacote de prova para PRs do Claude Code: diff, checks, URL pública, CTA e rollback.
Gate de revisão antes do commit com Claude Code
Revisão antes do commit com Claude Code: diff, build, URL pública, Gumroad, consultoria, testes e arquivos fora do escopo.