Acessibilidade na Web

Erros Comuns & Checklist

Os erros mais frequentes com exemplos de código · Checklist WCAG 2.2 AA

Erros Comuns e Como Corrigi-los

Imagens sem texto alternativo ou com alt genérico

Errado

<img src="grafico.png">
<img src="foto.jpg" alt="imagem">
<img src="logo.png" alt="logo">
<img src="icone.svg" alt="icon">

Correto

<!-- Informativa: descreva o conteúdo -->
<img src="grafico.png"
  alt="Vendas por região: Sul 45%, Sudeste 38%">

<!-- Logo: nome da empresa -->
<img src="logo.png" alt="Empresa XYZ">

<!-- Decorativa: alt vazio -->
<img src="divisor.png" alt=""
  role="presentation">
Foco visível removido com outline:none

Errado

/* Oculta o foco para TODOS */
* { outline: none; }
button:focus { outline: 0; }
a:focus { outline: none; }

Correto

/* Remove apenas para mouse,
   mantém para teclado */
:focus:not(:focus-visible) {
  outline: none;
}
:focus-visible {
  outline: 3px solid #facc15;
  outline-offset: 3px;
}
Janelas modais sem gestão de foco

Quando um modal abre: foco vai para o primeiro elemento focável dentro dele. Quando fecha: foco retorna ao elemento que o abriu. O foco deve ficar preso dentro do modal (focus trap).

<div role="dialog"
  aria-modal="true"
  aria-labelledby="modal-titulo"
  aria-describedby="modal-desc">

  <h2 id="modal-titulo">Confirmar exclusão</h2>
  <p id="modal-desc">
    Esta ação não pode ser desfeita.
  </p>
  <button>Confirmar</button>
  <button>Cancelar</button>
</div>
Tabelas sem cabeçalhos semânticos

Errado

<table>
  <tr>
    <td>Nome</td>
    <td>Idade</td>
  </tr>
  <tr>
    <td>Ana</td>
    <td>30</td>
  </tr>
</table>

Correto

<table>
  <caption>Lista de colaboradores</caption>
  <thead>
    <tr>
      <th scope="col">Nome</th>
      <th scope="col">Idade</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ana</td><td>30</td>
    </tr>
  </tbody>
</table>
Mensagens de erro sem associação ao campo

Errado

<input type="email" />
<span style="color:red">
  E-mail inválido
</span>

<!-- leitor de tela não sabe
     a qual campo pertence o erro -->

Correto

<label for="email">E-mail</label>
<input type="email" id="email"
  aria-invalid="true"
  aria-describedby="email-erro" />
<span id="email-erro" role="alert">
  Insira um e-mail válido:
  usuario@site.com
</span>
Conteúdo que aparece apenas no hover

Tooltips e menus que aparecem apenas no hover são inacessíveis para usuários de teclado e toque. O critério WCAG 1.4.13 (AA) exige que conteúdo que aparece no hover também:

  • Possa ser descartado sem mover o ponteiro ou foco (Esc)
  • Permaneça visível quando o ponteiro se move sobre ele
  • Persista até ser descartado pelo usuário ou não ser mais relevante

Use :hover + :focus-visible juntos, e implemente o Esc para fechar via JavaScript.

iframes sem título

Errado

<iframe src="mapa.html">
</iframe>

<iframe src="video-embed">
</iframe>

Correto

<iframe
  src="mapa.html"
  title="Mapa de localização da loja">
</iframe>

<iframe
  src="video-embed"
  title="Vídeo: Como usar o produto">
</iframe>
Linguagem da página não declarada

Errado — sem lang

<html>
  ...
</html>

<!-- Leitor de tela usa idioma
     padrão do sistema, podendo
     pronunciar PT com sotaque EN -->

Correto

<html lang="pt-br">

<!-- Trecho em outro idioma: -->
<p>
  O conceito de
  <span lang="en">
    responsive design
  </span>
  é fundamental.
</p>

Checklist WCAG 2.2 — Nível AA

Use como ponto de partida para auditar seu projeto. Lembre-se: ferramentas automáticas cobrem apenas ~35% dos problemas — o restante exige teste manual.

Estrutura e Semântica

  • ☐ Idioma declarado: <html lang="pt-br">
  • ☐ Um único <h1>, hierarquia de cabeçalhos correta
  • ☐ Landmarks HTML5 usados corretamente
  • ☐ Skip link como primeiro elemento focável
  • <title> descritivo e único por página
  • ☐ Múltiplas navegações com aria-label distintos
  • aria-current="page" no link ativo da navegação

Formulários

  • ☐ Todos os campos com <label> visível associado
  • ☐ Campos obrigatórios: indicador visual + aria-required
  • ☐ Erros descritivos vinculados com aria-describedby
  • autocomplete configurado para dados pessoais
  • ☐ Grupos com <fieldset> e <legend>
  • ☐ Erros identificados por texto, não só por cor
  • ☐ Sem CAPTCHA inacessível sem alternativa

Imagens e Mídia

  • ☐ Imagens informativas com alt significativo
  • ☐ Imagens decorativas com alt=""
  • ☐ Vídeos com legendas sincronizadas
  • ☐ Áudios com transcrição
  • ☐ Vídeos informativos com audiodescrição
  • ☐ Sem áudio automático sem controle
  • <iframe> com atributo title

Teclado e Foco

  • ☐ Toda funcionalidade acessível por teclado
  • ☐ Foco visível com contraste ≥ 3:1
  • ☐ Sem armadilha de foco não intencional
  • ☐ Ordem de tabulação lógica
  • ☐ Modais com focus trap e retorno de foco
  • ☐ Menus dropdown operáveis por teclado
  • Esc fecha modais, tooltips e popups

Cores e Visual

  • ☐ Contraste de texto ≥ 4,5:1 (normal) / 3:1 (grande)
  • ☐ Contraste de componentes UI ≥ 3:1
  • ☐ Informação não transmitida apenas por cor
  • ☐ Funciona com modo alto contraste do OS
  • ☐ Zoom 200% sem perda de conteúdo
  • ☐ Sem rolagem horizontal em 320px (zoom 400%)
  • ☐ Sem conteúdo que pisca >3 vezes/segundo

Conteúdo Dinâmico

  • ☐ Mudanças de status anunciadas via aria-live
  • ☐ Carrosséis com controles de pausa/anterior/próximo
  • ☐ Animações desativáveis (prefers-reduced-motion)
  • ☐ Timeouts avisam o usuário e permitem extensão
  • ☐ Conteúdo novo não move o foco inesperadamente
  • ☐ SPA atualiza o <title> na mudança de rota
  • ☐ Status de carregamento anunciado