Acessibilidade Web é boa engenharia: fundamentos e práticas
por Walter S. A. Souza, Desenvolvedor Full Stack
5 min de leituraQuando falamos em acessibilidade na web (a11y), é comum a indústria reduzir o assunto a ferramentas automatizadas avaliando contraste de cores, suporte a leitores de tela ou a adição frenética de atributos aria-*. Esses elementos são importantes, mas representam apenas o topo do iceberg.
Acessibilidade começa na maneira como pensamos a experiência e a arquitetura do produto.
Uma aplicação realmente acessível permite que pessoas com diferentes capacidades visuais, motoras, auditivas e cognitivas consigam perceber o conteúdo, compreender o contexto, navegar pela interface e, o mais importante, concluir suas tarefas com autonomia.
Na Up Web Studio, construímos aplicações educacionais, e-commerces e sistemas interativos. Se um aluno não consegue dar play em uma aula pelo teclado, ou se um cliente não entende o erro em um formulário de checkout porque a única indicação é uma borda vermelha, falhamos na engenharia da interface.
A referência internacional para esse trabalho é a WCAG 2.2 (Web Content Accessibility Guidelines), que organiza seus critérios na sigla POUR:
- Perceptível: A informação não pode ser invisível a nenhum sentido (visão, audição, etc).
- Operável: A interface não pode exigir uma interação que o usuário não consegue realizar (ex: uso exclusivo de mouse).
- Compreensível: A operação deve ser previsível e clara.
- Robusto: O código deve ser bem estruturado para ser interpretado por tecnologias assistivas atuais e futuras.
Abaixo, detalhamos como esses princípios se traduzem no ecossistema React.
Práticas Essenciais e Exemplos de Código
1. HTML Semântico antes de ARIA
O maior erro de acessibilidade em SPAs é reinventar a roda. Um <button> nativo já possui suporte a teclado (Enter e Espaço) e semântica compreendida por leitores de tela. Um <div onClick> não tem nada disso.
❌ Incorreto: Recriando botões com div.
// O usuário de teclado não consegue focar aqui usando 'Tab'
// O leitor de tela anuncia apenas como "grupo" ou texto simples.
<div
onClick={() => submitOrder()}
className="bg-blue-600 text-white p-2 rounded cursor-pointer"
>
Finalizar Compra
</div>;✅ Correto: Usando a plataforma a seu favor.
<button
onClick={() => submitOrder()}
type="button"
className="bg-blue-600 text-white p-2 rounded hover:bg-blue-700 transition-colors"
>
Finalizar Compra
</button>;2. Acessibilidade em Ícones Isolados
Um botão contendo apenas um ícone (lixeira, fechar, favoritar) não significa nada para quem não pode vê-lo. A interface não pode depender de interpretação visual.
❌ Incorreto: Ícone mudo.
<button onClick={deleteItem}>
<TrashIcon className="w-5 h-5 text-red-500" />
</button>;✅ Correto: Texto oculto visualmente ou aria-label.
<button
onClick={deleteItem}
title="Excluir item"
className="p-2 rounded hover:bg-gray-100"
>
{/* aria-hidden esconde o SVG do leitor de tela para não gerar ruído */}
<TrashIcon aria-hidden="true" className="w-5 h-5 text-red-500" />
{/* sr-only (Tailwind) mantém o texto disponível para tecnologias assistivas */}
<span className="sr-only">Excluir item</span>
</button>;3. Foco é Navegação, Não Detalhe Visual
O usuário precisa saber onde está. Remover o outline sem criar uma alternativa visual é o equivalente a apagar a luz de uma sala e pedir para alguém encontrar a porta.
❌ Incorreto: Removendo a indicação de foco.
<input
type="text"
className="border p-2 outline-none focus:outline-none" // 🚩 Interrompe a navegação
/>;✅ Correto: Utilizando pseudo-classes modernas.
<input
type="text"
className="border p-2 outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:border-blue-500"
/>;4. Estados Não Dependem Exclusivamente da Cor
Um campo inválido pode ficar vermelho, mas o daltonismo ou a baixa visão exigem que o erro seja comunicado por texto e associação semântica.
❌ Incorreto: Comunicação baseada apenas em cor.
<div>
<label>E-mail</label>
<input
type="email"
className={`border ${hasError ? 'border-red-500' : 'border-gray-300'}`}
/>
{hasError && <p className="text-red-500">Email inválido</p>}
</div>;✅ Correto: Atributos ARIA estabelecendo relação entre o erro e o campo.
<div>
<label htmlFor="email-input">E-mail</label>
<input
id="email-input"
type="email"
aria-invalid={hasError ? 'true' : 'false'}
aria-describedby={hasError ? 'email-error' : undefined}
className={`border ${hasError ? 'border-red-500' : 'border-gray-300'}`}
/>
{hasError && (
<p id="email-error" className="text-red-500 text-sm mt-1">
Por favor, insira um endereço de e-mail válido.
</p>
)}
</div>;O Papel (e o Limite) das Bibliotecas de Componentes
No ecossistema React, é comum utilizarmos bibliotecas maduras como Radix UI, Base UI ou React Aria. Elas fazem um trabalho excelente em abstrair a complexidade do WAI-ARIA (gerenciando roving tabindex em Menus, prendendo o foco dentro de Modais e gerenciando estados).
Porém, usar uma biblioteca acessível não torna a sua aplicação acessível automaticamente.
O shadcn/ui, por exemplo, entrega o código para a sua base. Se você remover o aria-label de um componente copiado, ou não passar o contexto correto para um <DialogTitle>, a acessibilidade é quebrada. Acessibilidade não é uma propriedade permanente de um componente importado; ela depende de como ele é composto, estilizado e integrado ao fluxo real da sua aplicação.
A Reflexão Principal: Uma Decisão de Arquitetura
O objetivo não deveria ser perguntar para a sua equipe: "Este componente possui atributos ARIA?"
A pergunta mais importante da engenharia de frontend é: "Uma pessoa consegue compreender e concluir esta tarefa sem depender da mesma forma de interação que eu utilizo?"
Essa mudança de perspectiva transforma a a11y de um checklist automatizado em uma fundação de produto.
- Um checkout de e-commerce acessível precisa permitir revisar o carrinho, entender falhas no cartão e finalizar a compra usando apenas um teclado.
- Uma plataforma educacional acessível precisa garantir que componentes complexos, mapas mentais interativos e quizzes forneçam feedback adequado não só visualmente, mas estruturalmente.
É justamente aí que a acessibilidade deixa de ser uma "correção" realizada no fim do projeto, após passar um linter como o Axe, e assume o seu verdadeiro papel: ser o pilar invisível (mas indispensável) da engenharia de uma boa interface.