Por que o desafio da acessibilidade continua impossível de resolver de verdade
A maioria dos testes de acessibilidade roda em ferramentas automatizadas como axe ou Lighthouse e entrega um relatório com erros óbvios. Elementos sem aria-label, contraste insuficiente, links sem texto descritivo. Passar nessa auditoria custa uma tarde de trabalho, dependendo do tamanho do projeto. Mas passar no teste não significa que a aplicação é acessível. É o primeiro equívoco comum que eu vejo equipes cometendo.
O que é o desafio da acessibilidade na prática
O desafio da acessibilidade não é só seguir checklists da WCAG. É entender que leitores de tela se comportam de forma diferente entre NVDA, JAWS e VoiceOver, que um padrão de tabulação lógico não é garantido automaticamente, e que alguns usuários navegam exclusivamente por teclado enquanto outros dependem de comandos de voz. Você vai passar horas tentando reproduzir um bug que só aparece quando alguém usa o Windows com NVDA no modo leitura, e nem sempre vai conseguir. Eu trabalhei em um projeto onde todos os testes automatizados passavam sem ressalvas. A navegação por teclado parecia funcional, os atributos ARIA estavam presentes, o contraste estava dentro dos limites. Quando testamos com um usuário real de leitor de tela, descobrimos que os modais de confirmação de ação travavam o foco permanentemente. O usuário ficava preso no modal e não tinha como sair. O problema era um erro no event listener que removia o ouvinte do botão Fechar quando a animação de entrada terminava, deixando apenas um tabindex zero sem event handler. A correção foi substituir a abordagem de animação por uma transição CSS pura e remover a lógica que manipulava os event listeners de forma dinâmica. Levar duas semanas para diagnosticar e três dias para corrigir completamente, incluindo testes com usuários.
Auditoria vs. teste com usuário real
Ferramentas automatizadas cobrem aproximadamente 30 a 40 por cento dos problemas que afetam pessoas com deficiência. O resto exige teste manual e, idealmente, teste com usuários reais. Isso custa dinheiro e tempo. Se o orçamento permitir, contrate pelo menos dois usuários com diferentes deficiências para navegar nas funcionalidades principais. Use o método Think Aloud e gravide a sessão. O que você vai ouvir provavelmente vai ser diferente do que assumiu nos requisitos. Um detalhe que poucas pessoas consideram: a ordem de leitura do VoiceOver no iOS é baseada na ordem DOM, mas a ordem de tabulação no navegador é baseada no foco do teclado. Quando você usa flexbox ou grid, a ordem visual dos elementos pode ser diferente da ordem no código. Isso quebra completamente a navegação por teclado se não for tratado com order CSS ou tabindex negativo. Eu já vi isso em pelo menos quatro projetos antes de me tornar cuidadoso com o assunto.
👉 Clique no botão abaixo para saber mais sobre o assunto!
ARIA não é solução mágica
O primeiro princípio da acessibilidade emARIA diz: não use ARIA se um elemento nativo já faz o trabalho. Isso parece simples até você enfrentar um componente customizado que precisa existir por questões de estilo ou reutilização. Aí começa o problema. Um select customizado que simula um dropdown nativo precisa ter role listbox, itens com role option, aria-selected atualizado dinamicamente, e navegação por setas que inclua rolagem automática até o item selecionado. Se um desses atributos estiver errado, o leitor de tela interpreta o componente completamente mal. Uma armadilha frequente: adicionar aria-live num container que recebe conteúdo atualizado por JavaScript. Se o valor de aria-live estiver como polite, as mudanças são lidas em segundo plano sem interromper o usuário. Se for assertive, o leitor corta o que está sendo dito no momento. Mudar o valor para off e depois para polite durante uma transição de estado pode fazer o leitor de tela repetir informações duplicadas. Evite essa prática. Mantenha o atributo constante e atualize apenas o conteúdo.
Gestão de foco em single-page applications
Em SPAs, a mudança de rota não recarrega a página, então o foco não volta ao topo automaticamente. A solução recomendada é mover o foco para umheading ou um elemento com tabindex="-1" logo após a navegação. O problema prático é que frameworks como React e Vue lidam com isso de formas diferentes. Em React, usar um useEffect com focus() no elemento certo pode funcionar, mas em SSR ou quando o componente monta de forma assíncrona, o foco pode ser perdido antes do DOM estar pronto. A alternativa mais segura é usar um micro-scheduler ou requestAnimationFrame para garantir que o DOM esteja renderizado antes de chamar focus(). Isso adiciona alguns milissegundos, mas resolve bugs intermitentes que aparecem em máquinas mais lentas.
Contraste e a armadilha dos temas dinâmicos
Verificar contraste com ferramentas como WebAIM Contrast Checker é rápido. O problema aparece quando o tema é dinâmico. Um botão que parece ter contraste suficiente em modo claro pode ter contraste insuficiente em modo escuro se as cores forem definidas por variáveis CSS mal planejadas. Uma variável de cor de texto herdada de um componente pai pode sobrescrever a cor esperada no contexto do tema escuro. A solução não é apenas testar os dois temas visualmente. É implementar testes automatizados de contraste em cada variante de tema e incluir isso no pipeline de CI, não apenas manualmente antes do lançamento.
O lado ruim que ninguém conta
Acessibilidade tem um custo alto de manutenção. Componentes atualizados precisam ser revisados, novas integrações com bibliotecas de terceiros podem quebrar padrões existentes, e mudanças de design frequently ignoram requisitos de tamanho mínimo de área de toque. Em projetos com muitos componentes customizados, o esforço de manter a acessibilidade ao longo do tempo pode ultrapassar o esforço inicial de desenvolvimento. Não há solução perfeita para isso. O melhor que se pode fazer é documentar as decisões de acessibilidade no design system e exigir revisão de acessibilidade em code review. Também vale avisar que algumas abordagens populares de internacionalização podem gerar problemas de acessibilidade. Strings traduzidas dinamicamente podem exceder o espaço disponível no layout, quebrando a exibição de tooltips ou labels. Usar lang attribute correto em cada seção ajuda leitores de tela a pronunciar corretamente, mas muitos desenvolvedores esquecem de atualizar esse atributo quando componentes são montados dinamicamente. O resultado é um leitor lendo português com sotaque inglês ou vice-versa, o que é confuso para usuários surdos que dependem de legendas ou de reconhecimento de língua para navegação.