Acessibilidade na Web

HTML Semântico & ARIA

Landmarks · Formulários · Links e Botões · Roles, Properties, States

Por que HTML Semântico?

Usar elementos HTML pelo seu significado semântico é a base da acessibilidade. Leitores de tela, motores de busca e outras tecnologias assistivas dependem da semântica para comunicar o conteúdo corretamente.

A regra de ouro: use o elemento certo para o trabalho certo. Um <button> para ações, um <a> para navegação, um <h1> para o título principal da página.

Landmarks (Marcos de Página)

Landmarks permitem que usuários de leitores de tela naveguem diretamente para seções principais. Pressionando D no NVDA ou percorrendo a lista de landmarks no VoiceOver.

Elementos landmark HTML5 e seus roles ARIA implícitos
Elemento HTMLRole implícitoUso
<header>bannerCabeçalho do site (apenas quando filho direto de body)
<nav>navigationGrupo de links de navegação; use aria-label se houver múltiplos
<main>mainConteúdo principal; apenas um por página
<aside>complementaryConteúdo tangencial ao principal
<footer>contentinfoRodapé do site (quando filho direto de body)
<section>regionApenas quando tem aria-labelledby ou aria-label
<form>formApenas quando tem nome acessível
<search>searchÁrea de busca (HTML5.3+)

Hierarquia de Cabeçalhos

Cabeçalhos criam um índice da página para leitores de tela. Nunca pule níveis por estética — use CSS para ajustar aparência visual.

Correto

<h1>Título da página</h1>
  <h2>Seção principal</h2>
    <h3>Subseção</h3>
    <h3>Outra subseção</h3>
  <h2>Segunda seção</h2>

Errado — pula do h1 para h4

<h1>Título</h1>
<h4>Subitem</h4>  <!-- pulo inválido -->

<!-- Use h2 e ajuste visualmente: -->
<h2 class="text-sm">Subitem</h2>

Deve haver apenas um <h1> por página, representando o título principal da página — não da empresa ou do site.

Formulários Acessíveis

Label vinculado ao campo

<label for="email">E-mail</label>
<input type="email" id="email"
  name="email" required
  aria-describedby="email-hint">
<p id="email-hint" class="hint">
  Ex: usuario@dominio.com
</p>

Grupo de campos com fieldset

<fieldset>
  <legend>Notificações</legend>
  <label>
    <input type="checkbox"
      name="email-notif">
    Por e-mail
  </label>
  <label>
    <input type="checkbox"
      name="sms-notif">
    Por SMS
  </label>
</fieldset>

Mensagem de erro acessível

<label for="cpf">CPF</label>
<input type="text" id="cpf"
  aria-invalid="true"
  aria-describedby="cpf-erro">
<span id="cpf-erro" role="alert">
  CPF inválido. Use o formato
  000.000.000-00
</span>

Autocomplete para dados pessoais

<input type="text"
  autocomplete="name"
  name="fullname">

<input type="email"
  autocomplete="email">

<input type="tel"
  autocomplete="tel">

<!-- WCAG 1.3.5 (AA): obrigatório
     para dados pessoais -->

Regra simples: use <a href> para navegar para um destino; use <button> para executar uma ação. Nunca use <div> ou <span> clicável sem semântica.

Links acessíveis

<!-- Texto descritivo -->
<a href="/sobre">Sobre nós</a>

<!-- Com ícone -->
<a href="doc.pdf"
  aria-label="Baixar relatório 2024 (PDF, 2MB)">
  <svg aria-hidden="true">...</svg>
  Baixar
</a>

<!-- Link externo -->
<a href="https://w3.org"
  target="_blank"
  rel="noopener"
  aria-label="W3C (abre em nova aba)">
  W3C
</a>

Botões acessíveis

<!-- Toggle com estado -->
<button type="button"
  aria-expanded="false"
  aria-controls="menu">
  Menu
</button>

<!-- Ação com ícone -->
<button type="button"
  aria-label="Excluir item">
  <svg aria-hidden="true">...</svg>
</button>

<!-- EVITE -->
<div onclick="...">Clique</div> ❌
<span role="button"
  tabindex="0">Clique</span> ⚠️

ARIA — Accessible Rich Internet Applications

Regra de ouro: prefira sempre HTML semântico nativo antes de usar ARIA. "No ARIA is better than bad ARIA." — W3C

Roles (papéis)

  • role="alert"
  • role="dialog"
  • role="tablist"
  • role="tab"
  • role="tabpanel"
  • role="status"
  • role="log"
  • role="progressbar"
  • role="tooltip"
  • role="menu"
  • role="menuitem"

Properties (propriedades)

  • aria-label
  • aria-labelledby
  • aria-describedby
  • aria-required="true"
  • aria-controls
  • aria-owns
  • aria-haspopup
  • aria-live
  • aria-atomic
  • aria-relevant

States (estados)

  • aria-expanded
  • aria-hidden
  • aria-checked
  • aria-selected
  • aria-disabled
  • aria-pressed
  • aria-current
  • aria-invalid
  • aria-busy

Regiões Live — anúncios dinâmicos sem mover o foco

<!-- Polido: não interrompe -->
<div aria-live="polite"
  aria-atomic="true">
  3 resultados encontrados
</div>

<!-- Urgente: interrompe imediatamente -->
<div role="alert"
  aria-live="assertive">
  Erro: sessão expirada!
</div>

<!-- Atalhos semânticos -->
<div role="status">
  Formulário enviado com sucesso.
</div>
  • Adicione o contêiner aria-live no HTML antes de inserir o conteúdo dinâmico
  • Use polite para a maioria dos casos — não interrompe a leitura atual
  • Use assertive apenas para erros críticos
  • role="status" = aria-live="polite" + aria-atomic="true"
  • role="alert" = aria-live="assertive" + aria-atomic="true"
  • Em SPAs, anuncie mudanças de rota via região live ou atualizando o <title>