Pular para o conteudo principal

Componentes React acessíveis

Como construir componentes interativos em React preservando semântica, foco e comportamento nativo.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

O problema

Tem um erro muito comum em frontend moderno:

o time cria um componente React bonito, flexível e cheio de props, mas destrói a base da interação no processo.

Aí aparecem coisas como:

  • div fingindo ser botão
  • span clicável com onClick
  • tabIndex={0} usado para compensar estrutura ruim
  • role="button" tentando salvar o que já nasceu torto

Na tela isso até pode parecer aceitável.

Mas o componente deixa de herdar comportamento que o navegador já resolvia bem.

E o pior: quando esse erro entra em design system ou biblioteca interna, ele se espalha pelo produto inteiro.

Modelo mental

Pensa assim:

componente acessível não é uma casca visual com atributos extras. É uma abstração que preserva a intenção e o comportamento do elemento certo.

Se a abstração obriga você a reconstruir com JavaScript o que o HTML nativo já faria de graça, tem uma boa chance de ela estar piorando a base.

Em React, isso importa ainda mais porque componente vira API de time.

Então a pergunta não é só:

  • “esse componente funciona aqui?”

A pergunta melhor é:

  • “esse componente continua seguro quando outras pessoas começarem a usá-lo em contextos diferentes?”

Quebrando o problema

Comece pela intenção, não pela aparência

Antes de estilizar, animação ou prop API, vale responder:

  • isso executa uma ação na mesma tela?
  • isso navega para outro lugar?
  • isso abre ou fecha algum contexto?

Se executa ação, o ponto de partida costuma ser <button>.

Se navega, costuma ser <a>.

Quando você começa pela aparência e não pela intenção, a semântica vira efeito colateral.

Elemento nativo compra muito comportamento de uma vez

Um botão nativo já traz:

  • foco
  • ativação por teclado
  • papel semântico claro
  • integração melhor com tecnologias assistivas

Quando você troca isso por div, precisa reconstruir quase tudo. E quase sempre reconstrói pela metade.

API flexível demais costuma esconder custo

Esse é um ponto importante em React.

Tem componente que aceita:

  • asChild
  • as
  • qualquer tag
  • qualquer prop

Isso pode ser útil.

Mas também pode facilitar o uso errado sem ninguém perceber cedo.

Se o componente permite virar qualquer coisa sem proteger comportamento essencial, ele parece poderoso e ao mesmo tempo fica perigoso.

aria-* não corrige fundação ruim

ARIA é útil quando você precisa descrever algo que o HTML sozinho não expressa bem.

Mas ARIA não transforma uma base ruim em base boa.

Se um botão virou div, o primeiro problema não é falta de aria-label.

O primeiro problema é que você abandonou o elemento certo.

Componentes de estado exigem clareza extra

Quando o componente muda de estado, o usuário precisa perceber isso.

Exemplos comuns:

  • botão toggle
  • acordeão
  • menu expansível
  • modal trigger

Nesses casos, não basta clicar e torcer para o visual explicar tudo.

Você precisa pensar em:

  • estado exposto de forma coerente
  • foco previsível
  • nome acessível claro
  • relação entre gatilho e conteúdo

Exemplo simples

Imagina um componente IconButton para abrir um modal de busca.

Versão ruim:

function IconButton({ onClick, children }) {
  return <div onClick={onClick}>{children}</div>;
}

Ele parece flexível.

Mas já começou devendo:

  • não é botão nativo
  • não herda comportamento de teclado corretamente
  • não comunica papel com clareza
  • vai exigir remendo sempre que o time quiser usá-lo direito

Versão melhor:

function IconButton({ onClick, label, children }) {
  return (
    <button type="button" onClick={onClick} aria-label={label}>
      {children}
    </button>
  );
}

Agora a abstração preserva a base certa.

Depois disso você pode discutir estilo, variante, tamanho, loading, disabled e o resto da API. Mas a fundação já não está errada.

Erros comuns

  • Criar componente base usando div por conveniência.
  • Achar que role="button" resolve tudo.
  • Testar abstração só no clique e ignorar teclado.
  • Expor API tão genérica que qualquer uso errado fica fácil.
  • Usar aria-* cedo demais em vez de corrigir semântica e comportamento.
  • Pensar em acessibilidade como adaptação final, não como requisito de modelagem.

Como um senior pensa

Quem tem mais experiência costuma desconfiar da própria abstração.

Principalmente em componente compartilhado.

A pergunta deixa de ser:

  • “deu para encapsular?”

e vira:

  • “essa encapsulação preserva o contrato nativo ou está escondendo uma dívida que o time vai pagar depois?”

Senioridade aqui aparece em duas coisas:

  • escolher o elemento base com mais disciplina
  • limitar a API para tornar o uso correto mais fácil que o uso errado

Isso é importante porque componente reutilizável multiplica qualidade quando nasce bem e multiplica problema quando nasce torto.

O que o entrevistador quer ver

Quando esse tema aparece em entrevista, o avaliador normalmente quer perceber se você:

  • entende que abstração de UI não pode apagar semântica
  • escolhe elemento pelo papel funcional
  • pensa em foco e teclado como parte do contrato do componente
  • sabe diferenciar uso legítimo de ARIA e remendo tardio

Uma resposta forte costuma soar assim:

Em componente React eu tento preservar o máximo possível do comportamento nativo. Se a abstração me faz reconstruir tecla, foco e papel semântico manualmente, eu já considero que talvez comecei do elemento errado. A API precisa ajudar o time a usar o componente certo do jeito certo.

Componente acessível não é o que recebe remendo no fim. É o que já nasce difícil de usar errado pelo time.

Se a abstração quebra o comportamento nativo, ela não está simplificando. Está terceirizando problema.

Resumo rápido

O que vale manter na cabeça

Checklist de pratica

Use isto ao responder

Você concluiu este artigo

Próximo artigo Como pensar tickets e tarefas Artigo anterior Teclado e foco

Continue explorando

Artigos relacionados