React Acessível
Todo app React renderiza para o mesmo HTML que o navegador sempre ofereceu, e cada ferramenta de acessibilidade funciona a partir do DOM que seus componentes produzem. Acessibilidade em React é basicamente uma série de pequenas escolhas sobre esse DOM: qual elemento você renderiza, como ele recebe seu nome, e o que você faz quando a tela muda para alguém que não consegue vê-la mudar.
Um detalhe de JSX antes do resto. React renomeia class para className e for para htmlFor, mas atributos ARIA mantêm seus hífens: aria-live, aria-label, e um simples role.
Elementos semânticos vêm em primeiro lugar
Um <button> chega com um monte de comportamento já anexado. Fica na sequência de tabulação, então um teclado consegue alcançá-lo. Dispara seu manipulador de clique ao pressionar Enter e Space. Um leitor de tela o anuncia como botão e lê seu texto como nome, que é também o nome que software de controle de voz alvo. O navegador lida com o estado desabilitado, o anel de foco, e o estilo ativo.
// o navegador oferece foco, ativação por teclado e o anúncio "button"
<button className="die" onClick={hold}>{value}</button>Uma <div> com um manipulador onClick recebe um item dessa lista: o clique. Tabulação pula por ela, Enter e Space não fazem nada, e um leitor de tela a lê como uma sequência de texto sem qualquer indicação de que algo acontecerá se você interagir com ela.
O remédio usual é role="button" mais tabIndex={0}, que coloca o elemento na sequência de tabulação e muda o que é anunciado. Comportamento ainda está faltando. Você adicionaria um manipulador onKeyDown, verificaria Enter e Space, chamaria preventDefault() em Space para que a página pare de rolar, e então manteria um estado desabilitado construído manualmente em sincronização com o estilo. Isso é bastante código para reconstruir algo que o navegador já oferece. Usar o <button> real é o caminho mais curto, e permanece correto conforme os navegadores mudam.
A mesma lógica funciona através do resto da marcação: <a href> para navegação, <nav> e <main> como pontos de referência entre os quais um leitor de tela pode pular, títulos em ordem para o esquema que as pessoas navegam. A maioria do trabalho de acessibilidade em uma base de código React é escolher o elemento que já faz o trabalho.
Anunciando o que mudou
Um app de página única se atualiza no mesmo lugar. Não há carregamento de página para informar um leitor de tela de que algo aconteceu, então uma mudança renderizada no meio da tela pode ser completamente silenciosa. Uma live region passa essa informação adiante: um container que o leitor de tela observa e anuncia sempre que seu conteúdo muda. A classe sr-only abaixo o oculta visualmente, usando um padrão CSS abordado posteriormente neste capítulo.
<div aria-live="polite" className="sr-only">
{isGameWon && <p>Você venceu! Pressione Novo Jogo para começar novamente.</p>}
</div>O wrapper renderiza a cada vez, vazio no início, e React coloca um parágrafo nele quando isGameWon muda. Essa ordem é a parte que as pessoas erram. O elemento carregando aria-live tem que estar no DOM antes do conteúdo chegar, porque leitores de tela registram live regions quando as encontram e depois observam mudanças. Monte a região e seu texto juntos em uma única renderização e muitos leitores de tela não anunciam nada: o todo parece conteúdo novo ordinário. Manter uma região vazia na árvore não custa nada e torna o anúncio confiável.
aria-live="polite" coloca o anúncio em uma fila. O leitor de tela termina o que está lendo, depois entrega sua mensagem na próxima pausa natural, que pode chegar um tempo depois da mudança visual. Esse atraso é deliberado, e polite é a configuração certa para quase tudo.
Interação por teclado
Tab move para frente através de elementos focáveis, Shift+Tab move para trás, Enter ativa links e botões, e Space ativa botões e alterna caixas de seleção.
A ordem de tabulação segue a ordem do DOM, então a sequência que seu JSX renderiza é a sequência pela qual as pessoas se movem. Reordenar visualmente com CSS deixa uma ordem de tabulação que salta pela tela, e valores tabIndex positivos causam a mesma confusão propositalmente. tabIndex={-1} é o útil: faz um elemento focável a partir de JavaScript enquanto o mantém fora da sequência de tabulação, que é o que um alvo de foco como um título de diálogo precisa.
Duas regras a mais. Mantenha o foco visível: evite outline: none a menos que um estilo :focus-visible seu próprio o substitua. E mantenha uma saída disponível: um modal que deliberadamente mantém o foco dentro de si precisa de Escape para fechar e precisa devolver o foco ao seu gatilho.
Movendo o foco deliberadamente
Quando a UI muda de forma, o foco pode acabar em lugar nenhum. Alguém ativa um botão, o botão é removido ou substituído, e o foco volta para <body>. O próximo Tab começa no topo da página, e o leitor perdeu seu lugar.
O remédio é mover o foco para algo sensato, que é um dos usos legítimos de uma ref:
function NewGameButton({ isGameWon, onNewGame }) {
const buttonRef = useRef(null)
useEffect(() => {
if (isGameWon) {
buttonRef.current.focus()
}
}, [isGameWon])
return <button ref={buttonRef} onClick={onNewGame}>Novo Jogo</button>
}O efeito roda depois que React confirmou aquele nó na tela, então o elemento está lá para receber o foco. Guarding em isGameWon o evita de roubar foco em cada renderização.
O mesmo padrão cobre os outros momentos comuns: um diálogo toma foco ao abrir e o devolve ao gatilho ao fechar, uma falha de validação envia foco para o primeiro campo inválido, deletar uma linha move foco para a linha que a substituiu. A regra por baixo é uma linha: se seu código removeu a coisa que tinha foco, seu código decide para onde o foco vai depois.
Texto visualmente oculto
Bastante status é óbvio a partir do layout e silencioso para um leitor de tela: um checkmark verde ao lado de um campo, um dado que parece pressionado, um número que é legível de onde está. Texto visualmente oculto soletra isso para qualquer um ouvindo a página.
A convenção é uma classe chamada sr-only. Não tem significado para React ou para o navegador: é um nome de classe simples, e essas regras CSS são o que faz o trabalho.
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}O elemento permanece na árvore de acessibilidade enquanto não ocupa espaço visual. display: none e visibility: hidden o removeriam dessa árvore também, ocultando-o de todos.
Um botão apenas com ícone é o caso cotidiano. Ou dê-lhe um aria-label, ou coloque texto real dentro e o oculte visualmente:
<button onClick={onClose}>
<XIcon aria-hidden="true" />
<span className="sr-only">Fechar</span>
</button>aria-hidden="true" mantém o SVG decorativo fora do anúncio, e o span oculto fornece o nome. Uma ressalva sobre aria-label: define um nome acessível em elementos interativos e em qualquer coisa carregando um role explícito, e navegadores frequentemente o ignoram em uma <div> ou <span> simples sem role. Mantenha-o para botões, links, inputs, e landmarks rotulados.
Ler o código só vai levar você até certo ponto com qualquer isto. Ative VoiceOver com Cmd+F5 e ouça seu próprio app, e execute axe DevTools no navegador para pegar rótulos faltando e controles sem nome automaticamente.
button real quando algo é clicável, e um label real próximo a cada input. Esses elementos vêm com suporte a teclado e um nome que um leitor de tela consegue ler, tudo de graça. Quando algo muda na tela que uma pessoa ouvindo a página de outra forma perderia, coloque uma frase curta dentro de uma div com aria-live="polite", e mantenha essa div na página desde o início para que a mudança seja percebida. Próximo: Além o básico, um mapa do que vem depois dos fundamentos.

