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.
| Elemento HTML | Role implícito | Uso |
|---|---|---|
<header> | banner | Cabeçalho do site (apenas quando filho direto de body) |
<nav> | navigation | Grupo de links de navegação; use aria-label se houver múltiplos |
<main> | main | Conteúdo principal; apenas um por página |
<aside> | complementary | Conteúdo tangencial ao principal |
<footer> | contentinfo | Rodapé do site (quando filho direto de body) |
<section> | region | Apenas quando tem aria-labelledby ou aria-label |
<form> | form | Apenas 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 -->
Links e Botões
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
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-labelaria-labelledbyaria-describedbyaria-required="true"aria-controlsaria-ownsaria-haspopuparia-livearia-atomicaria-relevant
States (estados)
aria-expandedaria-hiddenaria-checkedaria-selectedaria-disabledaria-pressedaria-currentaria-invalidaria-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-liveno HTML antes de inserir o conteúdo dinâmico - Use
politepara a maioria dos casos — não interrompe a leitura atual - Use
assertiveapenas 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>