Acessibilidade Web é boa engenharia: fundamentos e práticas

por Walter S. A. Souza, Desenvolvedor Full Stack

5 min de leitura

Quando 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.

Referências

Mais artigos

Radix UI, Base UI ou React Aria? Como escolher a base do seu Design System no shadcn/ui

Radix, Base UI ou React Aria? O ecossistema do shadcn/ui mudou e agora suporta três grandes bases headless. Entenda a filosofia por trás de cada uma, veja quem ganha em acessibilidade e descubra qual escolher para o seu próximo projeto React.

Leia mais

Além da lousa: criando laboratórios virtuais e componentes STEM interativos com React

Veja como criar laboratórios virtuais e componentes STEM interativos com React, usando bibliotecas como Mafs, Euclid.js, JSXGraph e math.js com foco em performance, arquitetura e experiência educacional.

Leia mais

Vamos tirar seu projeto do papel?

Conectados ao seu negócio

  • Endereço fiscal
    Av. Paulista, 1106 - Sala 01 Andar 16
    Bela Vista, São Paulo - SP
    CEP 01310-914
  • Atendimento nacional
    Projetos em todo o Brasil
    Trabalho remoto, colaborativo e orientado a resultados