Como auditar os critérios novos do WCAG 2.2 no seu design system
Um passo a passo para times brasileiros testarem modal, carrossel e formulário multi-etapa contra Focus Not Obscured, Dragging Movements e Redundant Entry, com axe-core e teclado.

O WCAG 2.2 virou Recommendation do W3C em 12 de dezembro de 2024. Ele é aditivo em relação ao 2.1: quem já conformava com 2.1 continua conformando, mas agora tem nove critérios novos para cobrir (e um, o 4.1.1 Parsing, foi removido). Neste tutorial eu foco em três que batem direto nos componentes mais comuns de qualquer design system↳Design system5 conteúdosComo desenvolvemos o novo Design System do AsaasProduto & UX · jul 2024UX e Código: Por que designers que conhecem programação têm uma vantagem estratégicaProduto & UX · abr 2025A importância do HTML e CSS para quem trabalha com UI Design e Design SystemProduto & UX · dez 2024Ver tudo em Produto & UX → e que a maioria dos times ainda não testa: 2.4.11 Focus Not Obscured (Minimum, AA), 2.5.7 Dragging Movements (AA) e 3.3.7 Redundant Entry (A).
O ponto central do ângulo aqui é: não adianta corrigir a página isolada. Se o modal do design system esconde o foco, ele esconde em todas as 40 telas que o usam. O fix mora no componente.
Pré-requisitos
Rodei tudo com Node 20, e as versões que uso no exemplo:
axe-core4.10@axe-core/playwright4.10 (para rodar em cima de componentes renderizados)@playwright/test1.48
npm i -D @axe-core/playwright @playwright/test axe-core
npx playwright install chromiumUma coisa que preciso deixar clara logo de cara: axe-core não detecta os três critérios deste artigo de forma confiável. Focus Not Obscured e Dragging Movements dependem de estado (foco ativo, gesto de arrastar) e de julgamento sobre alternativas; Redundant Entry depende de entender o fluxo. O axe entra como rede de segurança para o resto (contraste, nome acessível, roles). O achado dos três critérios novos vem do teste manual de teclado. Quem promete auditoria 100% automatizada de WCAG 2.2 está vendendo ilusão.
Montando a base automatizada
Começo garantindo que o componente não tem regressões óbvias. Um teste por componente do design system, apontando para o Storybook ou uma página de sandbox:
// a11y.spec.js
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('modal não tem violações axe', async ({ page }) => {
await page.goto('http://localhost:6006/iframe.html?id=modal--default');
await page.getByRole('button', { name: 'Abrir' }).click();
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
.analyze();
expect(results.violations).toEqual([]);
});O withTags(['wcag22aa']) já filtra pelas regras que o axe consegue mapear ao 2.2. Rode e limpe o que aparecer antes de partir para o manual, senão você mistura ruído de contraste com o achado real de foco.
Critério 2.4.11: Focus Not Obscured no modal
O que o critério exige (nível AA): quando um elemento recebe foco por teclado, ele não pode ficar totalmente escondido por outro conteúdo criado pela página (barra fixa, cookie banner, modal, sticky header).
Onde quebra num design system: o clássico é o rodapé sticky ou o header fixo. O usuário tabula por um formulário longo, o campo focado escorrega para baix do sticky footer e desaparece. Como o footer é um componente compartilhado, o bug se propaga.
Como auditar, na mão:
- Abra o componente/página. Não use o mouse.
Tabdo topo até o fim, devagar.- A cada parada, pergunte: consigo ver o indicador de foco por inteiro, ou ao menos parte dele? Se o elemento sumiu completamente atrás de algo sticky, é violação de 2.4.11.
Um heurístico que ajuda no debug: no console, document.activeElement.getBoundingClientRect() e compare com a área coberta pelo elemento fixo.
O fix no componente, não na página: reserve espaço para o conteúdo fixo com scroll-padding no container rolável, para que o scroll automático do foco pare antes de entrar sob o sticky.
/* no componente de layout, não numa página */
.app-scroll-container {
scroll-padding-bottom: var(--sticky-footer-height, 72px);
scroll-padding-top: var(--sticky-header-height, 56px);
}O scroll-padding faz o browser respeitar a margem ao trazer o elemento focado para a viewport. Como o valor vem de um token (--sticky-footer-height), qualquer tela que use o footer herda o comportamento correto.
Critério 2.5.7: Dragging Movements no carrossel
O que exige (AA): toda funcionalidade que usa arrastar precisa ter uma alternativa que não dependa de arrastar, a menos que arrastar seja essencial. Botão, tap, seta, o que for, desde que single-pointer sem gesto.
Onde quebra: carrossel que só avança com swipe/drag, slider de preço que só move arrastando o thumb, kanban↳Kanban3 conteúdosComunicação ágil e automatizada com JiraDev (Back & Front) · set 2019Planejamento do roadmap de desenvolvimento 2025: dicas ágeis e tecnologiasDev (Back & Front) · jan 2025Zappts abre 14 vagas em modelo home officeDev (Back & Front) · jun 2022Ver tudo em Gestão Dev & TI → que só reordena com drag-and-drop. Todos são componentes de biblioteca, então o fix vale para todos os consumidores.
Como auditar:
- Tente operar o componente só com clique/tap simples e com teclado. Nada de arrastar.
- No carrossel: existem botões "anterior/próximo" que funcionam? As setas do teclado mudam o slide quando ele está focado?
- No slider: dá para clicar na trilha para posicionar, ou usar
ArrowLeft/ArrowRight?
Se a única forma de mover é arrastando, falhou.
O fix no componente do carrossel:
<div role="group" aria-roledescription="carrossel" aria-label="Destaques">
<button aria-label="Slide anterior" onClick={prev}>‹</button>
<div className="track" onKeyDown={(e) => {
if (e.key === 'ArrowRight') next();
if (e.key === 'ArrowLeft') prev();
}} tabIndex={0}>
{/* slides */}
</div>
<button aria-label="Próximo slide" onClick={next}>›</button>
</div>Mantenha o swipe para quem quer, mas ele nunca pode ser o único caminho. Para o slider nativo, já resolve teclado de graça, o que é um argumento forte contra reinventar o controle com uma div arrastável.
Critério 3.3.7: Redundant Entry no formulário multi-etapa
O que exige (nível A, ou seja, o piso): informação que o usuário já forneceu no mesmo processo deve ser autopreenchida ou disponível para seleção, sem obrigar a redigitar. Exceções: quando redigitar é essencial (confirmar senha), a informação anterior não é mais válida, ou por segurança.
Onde quebra: checkout que pede "endereço de cobrança" logo depois do "endereço de entrega" sem opção de "usar o mesmo". Wizard de cadastro que perde os dados ao voltar uma etapa. Como o wizard costuma ser um componente de orquestração no design system, dá para resolver no lugar certo.
Como auditar:
- Percorra o fluxo inteiro anotando cada campo pedido.
- Marque qualquer dado solicitado duas vezes.
- Para cada repetição, pergunte: existe autofill, ou um "igual ao anterior"? A repetição é essencial (confirmação de senha)? Se não é essencial e não tem atalho, é violação.
O fix no componente de wizard: persista o estado entre etapas e exponha o "copiar do passo anterior".
<Checkbox
label="Endereço de cobrança igual ao de entrega"
checked={sameAsShipping}
onChange={(v) => {
setSameAsShipping(v);
if (v) copyFields('shipping', 'billing');
}}
/>Use também autocomplete correto nos inputs (autocomplete="postal-code", "street-address") para o browser ajudar. É um detalhe barato que atende o espírito do critério.
Fechando o loop: achado, critério, componente
O que mudou de verdade na minha rotina de auditoria foi parar de reportar "bug na tela X". Cada achado vira uma linha assim: slide só avança por swipe → 2.5.7 → componente Carousel → adicionar botões prev/next + navegação por seta. Assim o fix entra na definição de pronto do componente e não reaparece.
Dois pontos ficam em aberto e vale acompanhar. Primeiro, a maturidade das ferramentas: o axe-core cobre bem 2.1, mas os critérios de 2.2 que dependem de estado ainda exigem julgamento humano, então planeje tempo de QA manual. Segundo, política: no Brasil, o eMAG e discussões de acessibilidade digital ainda referenciam versões anteriores, mas como o WCAG 2.2 é retrocompatível, mirar nele já cobre 2.0 e 2.1 e antecipa qualquer atualização regulatória. Para checar cada critério em detalhe, o material canônico é o How to Meet WCAG 2.2 e o Understanding WCAG 2.2, do próprio W3C.
Este artigo foi escrito por Yara Uchôa, colunista de UX e product design do iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters e validação técnica de Tiago Rosa. Saiba como produzimos no expediente.










Comentários
Ninguém comentou ainda. Começa a conversa?