Voltar ao blog
Tutorial

Acessibilidade web: o básico que todo dev ignora

Por Flávio Emanuel · · 9 min de leitura

Acessibilidade não é favor. É requisito.

A maioria dos devs trata acessibilidade como extra. Algo pra fazer “se sobrar tempo”. Na prática, nunca sobra. O site vai pro ar sem alt text nas imagens, sem contraste adequado, sem navegação por teclado, e sem semântica HTML correta.

O resultado: 15% da população mundial tem algum tipo de deficiência. No Brasil, são mais de 45 milhões de pessoas segundo o IBGE. Parte desse público usa leitor de tela, navega por teclado, ou depende de contraste alto pra ler conteúdo. Se o site não é acessível, essas pessoas não conseguem usar.

Além do argumento ético, tem o legal. A Lei Brasileira de Inclusão (Lei 13.146/2015) exige que sites sejam acessíveis. Na prática, a fiscalização ainda é fraca, mas processos judiciais por falta de acessibilidade digital estão aumentando. Nos EUA, o número de processos por acessibilidade web passou de 4.000 por ano.

E tem o argumento de SEO. Google usa sinais de acessibilidade pra ranquear. Alt text em imagens, heading hierarchy correta, semântica HTML, contraste adequado. Tudo que melhora acessibilidade melhora SEO. São as mesmas práticas.

Semântica HTML: o fundamento que ninguém respeita

HTML semântico significa usar as tags certas pro conteúdo certo. <nav> pra navegação, <main> pro conteúdo principal, <article> pra artigo, <button> pra botão. Parece óbvio, mas a quantidade de sites que usa <div> pra tudo é grande.

O problema com <div> pra tudo: leitor de tela não sabe o que é aquele elemento. Um <div onclick="..."> parece um botão visualmente, mas o leitor de tela não anuncia como botão, não responde a tecla Enter, e não aparece na lista de elementos interativos.

<!-- Errado -->
<div class="btn" onclick="submit()">Enviar</div>

<!-- Certo -->
<button type="submit">Enviar</button>

A diferença parece pequena no código. Na experiência de quem usa leitor de tela, é a diferença entre conseguir usar o site ou não.

Estrutura semântica mínima que todo site precisa:

<header>
  <nav>...</nav>
</header>
<main>
  <h1>Título único da página</h1>
  <section>
    <h2>Seção</h2>
    ...
  </section>
</main>
<footer>...</footer>

Isso custa zero tempo extra de desenvolvimento. É a diferença entre HTML que faz sentido e HTML que é só visual.

No GPM2, no Family Pilates e em todos os meus projetos Astro, a estrutura semântica vem por padrão. <header>, <nav>, <main>, <footer>, headings em hierarquia correta. Não é feature extra. É como o HTML deveria ser escrito desde o início.

Alt text: a tag mais ignorada da web

Toda imagem precisa de atributo alt. Sem exceção. O alt text é o que o leitor de tela lê quando encontra uma imagem. Sem ele, a pessoa ouve “imagem” e não sabe o que é.

Regras práticas pra alt text:

Imagens informativas: descreva o que a imagem mostra. “Estúdio de Pilates com equipamento reformer e espelho” é melhor que “foto do estúdio”.

Imagens decorativas: use alt="" (alt vazio, não sem alt). Isso diz pro leitor de tela pular a imagem. Ícones decorativos, backgrounds e separadores visuais entram aqui.

Imagens com texto: inclua o texto da imagem no alt. Se a imagem tem “30% de desconto”, o alt precisa dizer “30% de desconto”.

Logos: “Logo da [empresa]” é suficiente.

O alt text também ajuda no SEO. Google lê o alt pra entender o conteúdo da imagem. Keyword natural no alt (quando faz sentido) melhora ranqueamento em Google Images. Detalhei os limites de caracteres pra alt text no post sobre SEO técnico pra devs: entre 80 e 125 caracteres, descritivo e com keyword quando possível.

Contraste: se não dá pra ler, não existe

WCAG (Web Content Accessibility Guidelines) define ratios mínimos de contraste entre texto e fundo:

  • Texto normal: ratio mínimo de 4.5:1
  • Texto grande (18px+ bold ou 24px+ regular): ratio mínimo de 3:1
  • Elementos interativos (botões, links, inputs): ratio mínimo de 3:1

Na prática: texto cinza claro (#999) em fundo branco (#FFF) tem ratio de 2.85:1. Reprova. Texto cinza escuro (#595959) em fundo branco tem ratio de 7:1. Passa com folga.

O design minimalista com tons pastéis e baixo contraste é bonito no Dribbble e inutilizável pra quem tem baixa visão. E “baixa visão” não é só deficiência diagnosticada. É qualquer pessoa acima de 45 anos usando o celular sob luz do sol.

Ferramenta gratuita pra testar: WebAIM Contrast Checker (webaim.org/resources/contrastchecker). Coloca a cor do texto e do fundo e ele mostra se passa ou não. Leva 10 segundos por par de cores.

No Lighthouse, a auditoria de acessibilidade já verifica contraste automaticamente. Se o score de acessibilidade tá abaixo de 90, provavelmente tem problema de contraste.

Abra seu site e tente navegar usando só o teclado. Tab pra avançar entre elementos, Shift+Tab pra voltar, Enter pra ativar. Se você não consegue acessar todos os links, botões e formulários, quem usa teclado também não consegue.

Problemas comuns:

Foco invisível. O navegador mostra um outline azul no elemento focado. Muitos devs removem com outline: none porque “é feio”. Isso remove o indicador visual pra quem navega por teclado. Nunca remova outline sem substituir por outro indicador visível.

/* Errado */
*:focus { outline: none; }

/* Certo */
*:focus-visible {
  outline: 2px solid #0F8A6B;
  outline-offset: 2px;
}

focus-visible mostra o outline só quando o usuário está navegando por teclado, não quando clica com mouse. Melhor dos dois mundos.

Ordem de tab incoerente. O foco pula de um canto pro outro da página porque o HTML não está na ordem visual. Resolva organizando o HTML na ordem lógica de leitura. Não use tabindex positivo (tabindex=“1”, tabindex=“2”). Use a ordem natural do DOM.

Elementos interativos não focáveis. <div> com click handler não recebe foco por teclado. Use <button> ou <a> pra elementos interativos. Se precisar de div por algum motivo, adicione tabindex="0" e role="button".

Modal que prende o foco. Abriu um modal? O foco precisa ficar dentro dele até fechar. Se o usuário aperta Tab e o foco vai pra página atrás do modal, a experiência quebra. Use inert no conteúdo atrás do modal ou implemente focus trap.

Formulários acessíveis

Formulário sem label é formulário inacessível. O leitor de tela precisa do <label> associado ao <input> pra anunciar o que cada campo espera.

<!-- Errado: placeholder não substitui label -->
<input type="email" placeholder="Seu email">

<!-- Certo -->
<label for="email">Seu email</label>
<input type="email" id="email" name="email">

Placeholder some quando o usuário começa a digitar. Se era a única indicação do que o campo pede, o usuário esquece o que deveria preencher. Label é permanente.

Mensagens de erro precisam ser associadas ao campo. Use aria-describedby:

<label for="email">Email</label>
<input type="email" id="email" aria-describedby="email-error">
<span id="email-error" role="alert">Email inválido</span>

O leitor de tela anuncia o erro vinculado ao campo. Sem essa associação, a pessoa sabe que tem erro na página mas não sabe em qual campo.

No AutoPars, formulários de cadastro e checkout seguem essas práticas. Labels visíveis, mensagens de erro associadas, e navegação completa por teclado. É um marketplace onde vendedores cadastram peças pelo celular, muitas vezes com uma mão só. Acessibilidade aqui não é sobre deficiência, é sobre usabilidade em contexto real.

ARIA: use com moderação

ARIA (Accessible Rich Internet Applications) são atributos que adicionam informação de acessibilidade a elementos HTML. O problema: ARIA mal usado é pior que ARIA nenhum.

A primeira regra do ARIA: se você pode usar HTML semântico nativo, não use ARIA. <button> já é acessível. <div role="button"> é uma versão pior do mesmo.

Onde ARIA faz sentido:

  • aria-label pra elementos sem texto visível (ícone de menu hamburger: aria-label="Abrir menu")
  • aria-expanded pra menus dropdown (indica se tá aberto ou fechado)
  • aria-live pra conteúdo que atualiza dinamicamente (notificações, contadores)
  • role="alert" pra mensagens de erro

Onde ARIA não faz sentido:

  • role="button" em <div> (use <button>)
  • role="navigation" em <div> (use <nav>)
  • aria-label repetindo o texto visível do elemento (redundante)

Como testar acessibilidade

Não precisa de ferramenta cara. O básico se resolve com:

Lighthouse (Chrome DevTools). Roda auditoria de acessibilidade automática. Pega problemas de contraste, alt text faltando, heading hierarchy, e elementos sem label.

Navegação por teclado. Abre o site e navega só com Tab e Enter por 2 minutos. Se não consegue acessar tudo, tem problema.

Leitor de tela. No Mac, VoiceOver vem instalado (Cmd+F5). No Windows, NVDA é gratuito. Liga o leitor e tenta usar o site. A experiência é reveladora.

axe DevTools (extensão Chrome). Roda auditoria mais detalhada que o Lighthouse. Mostra exatamente qual elemento tem problema e como corrigir.

O ideal é testar com pelo menos dois desses métodos em todo projeto antes de entregar. No meu fluxo, Lighthouse de acessibilidade acima de 90 é meta mínima, junto com teste manual de teclado.

Checklist de acessibilidade mínima

  • HTML semântico (header, nav, main, footer, section)
  • H1 único por página, hierarquia H2 > H3 sem pular níveis
  • Alt text em todas as imagens (descritivo ou vazio pra decorativas)
  • Contraste mínimo 4.5:1 pra texto normal
  • Navegação completa por teclado (Tab, Enter, Escape)
  • Focus visível em todos os elementos interativos
  • Labels em todos os campos de formulário
  • Mensagens de erro associadas ao campo com aria-describedby
  • Idioma da página definido (<html lang="pt-BR">)
  • Lighthouse Accessibility acima de 90

Nenhum desses itens adiciona tempo significativo ao desenvolvimento. A maioria é HTML correto desde o início, não correção retroativa. Implementar custa quase nada. Ignorar custa o acesso de milhões de pessoas ao seu site.

Próximo passo

Precisa de um dev que entrega de verdade?

Seja pra um projeto pontual, reforço no time, ou parceria de longo prazo. Vamos conversar.

Falar no WhatsApp

Respondo em até 2h durante horário comercial.