React Accesible
Toda aplicación React se renderiza al mismo HTML que el navegador siempre ha incluido, y cada herramienta de asistencia trabaja a partir del DOM que tus componentes producen. La accesibilidad en React es principalmente una serie de decisiones pequeñas sobre ese DOM: qué elemento renderizas, cómo obtiene su nombre, y qué haces cuando la pantalla cambia para alguien que no puede verlo cambiar.
Un detalle de JSX antes de lo demás. React renombra class a className y for a htmlFor, pero los atributos ARIA mantienen sus guiones: aria-live, aria-label, y un role común.
Los elementos semánticos van primero
Un <button> llega con un montón de comportamiento ya incorporado. Se sitúa en el orden de tabulación, así que el teclado puede alcanzarlo. Activa su controlador de clic en Enter y en Espacio. Un lector de pantalla lo anuncia como un botón y lee su texto como el nombre, que también es el nombre que el software de control de voz busca. El navegador se encarga del estado deshabilitado, del anillo de foco y del estilo activo.
// el navegador te da foco, activación por teclado y el anuncio "button"
<button className="die" onClick={hold}>{value}</button>Un <div> con un controlador onClick obtiene un elemento de esa lista: el clic. Tab lo salta, Enter y Espacio no hacen nada, y un lector de pantalla lo lee como un conjunto de texto sin pista alguna de que algo sucederá si interactúas con él.
El parche habitual es role="button" más tabIndex={0}, que coloca el elemento en el orden de tabulación y cambia lo que se anuncia. El comportamiento sigue faltando. Tendrías que agregar un controlador onKeyDown, verificar Enter y Espacio, llamar a preventDefault() en Espacio para que la página deje de desplazarse, y mantener un estado deshabilitado hecho a mano sincronizado con el estilo. Eso es una cantidad considerable de código para reconstruir algo que el navegador ya proporciona. Recurrir al verdadero <button> es el camino más corto, y se mantiene correcto a medida que los navegadores cambian.
La misma lógica se extiende al resto del marcado: <a href> para navegación, <nav> y <main> como puntos de referencia por los que un lector de pantalla puede saltar, encabezados en orden para el esquema por el que la gente navega. La mayoría del trabajo de accesibilidad en una base de código React consiste en elegir el elemento que ya hace el trabajo.
Anunciando qué cambió
Una aplicación de una sola página se actualiza en el lugar. No hay carga de página que le diga a un lector de pantalla que algo sucedió, así que un cambio renderizado a mitad de la pantalla puede ser completamente silencioso. Una región activa le pasa esa información: un contenedor que el lector de pantalla observa y anuncia cada vez que su contenido cambia. La clase sr-only a continuación la oculta visualmente, usando un patrón CSS que se cubre más adelante en este capítulo.
<div aria-live="polite" className="sr-only">
{isGameWon && <p>¡Ganaste! Presiona Nuevo Juego para empezar de nuevo.</p>}
</div>El contenedor se renderiza cada vez, vacío al principio, y React intercambia un párrafo en él cuando isGameWon cambia. Ese orden es la parte que la gente se equivoca. El elemento que lleva aria-live tiene que estar en el DOM antes de que llegue el contenido, porque los lectores de pantalla registran regiones activas cuando las encuentran y luego observan mutaciones. Montar la región y su texto juntos en un único renderizado y muchos lectores de pantalla no anuncian nada: todo parece contenido nuevo ordinario. Mantener una región vacía en el árbol no cuesta nada y hace que el anuncio sea confiable.
aria-live="polite" coloca el anuncio en una cola. El lector de pantalla termina lo que está leyendo actualmente, luego entrega tu mensaje en la siguiente pausa natural, que puede ocurrir un instante después del cambio visual. Ese retraso es deliberado, y polite es la configuración correcta para casi todo.
Interacción con el teclado
Tab avanza a través de elementos enfocables, Shift+Tab retrocede, Enter activa enlaces y botones, y Espacio activa botones y alterna casillas de verificación.
El orden de tabulación sigue el orden del DOM, así que la secuencia que tu JSX renderiza es la secuencia por la que la gente se mueve. Reordenar visualmente con CSS deja un orden de tabulación que salta alrededor de la pantalla, y los valores positivos de tabIndex causan la misma confusión a propósito. tabIndex={-1} es el útil: hace que un elemento sea enfocable desde JavaScript mientras lo mantiene fuera de la secuencia de tabulación, que es lo que un objetivo de foco como un encabezado de diálogo necesita.
Dos reglas más. Mantén el foco visible: evita outline: none a menos que un estilo :focus-visible propio lo reemplace. Y mantén una salida disponible: un modal que deliberadamente mantiene el foco dentro de sí mismo necesita Escape para cerrar y necesita devolver el foco a su disparador.
Moviendo el foco deliberadamente
Cuando la UI cambia de forma, el foco puede acabar en ningún lugar. Alguien activa un botón, el botón se elimina o reemplaza, y el foco vuelve a <body>. El siguiente Tab comienza en la parte superior de la página, y el lector ha perdido su lugar.
La solución es mover el foco a un lugar sensato, que es uno de los usos legítimos de un ref:
function NewGameButton({ isGameWon, onNewGame }) {
const buttonRef = useRef(null)
useEffect(() => {
if (isGameWon) {
buttonRef.current.focus()
}
}, [isGameWon])
return <button ref={buttonRef} onClick={onNewGame}>Nuevo Juego</button>
}El effect se ejecuta después de que React haya confirmado ese nodo en la pantalla, así que el elemento está allí para recibir foco. Proteger con isGameWon lo evita robar foco en cada renderizado.
El mismo patrón cubre los otros momentos comunes: un diálogo toma el foco al abrirse y lo devuelve al disparador al cerrarse, una falla de validación envía el foco al primer campo inválido, eliminar una fila mueve el foco a la fila que la reemplazó. La regla subyacente es una línea: si tu código eliminó lo que tenía el foco, tu código decide a dónde va el foco después.
Texto visualmente oculto
Hay muchísimo estado que es obvio del layout y silencioso para un lector de pantalla: una marca de verificación verde junto a un campo, un dado que se ve presionado, un número que se lee claramente de donde está. El texto visualmente oculto explica eso para cualquiera que esté escuchando la página.
La convención es una clase llamada sr-only. No tiene significado para React ni para el navegador: es un nombre de clase simple, y estas reglas CSS son las que hacen el trabajo.
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}El elemento se mantiene en el árbol de accesibilidad mientras no ocupa espacio visual. display: none y visibility: hidden lo eliminarían del árbol también, ocultándolo de todos.
Un botón solo con ícono es el caso cotidiano. O dale un aria-label, o pon texto real dentro y ocúltalo visualmente:
<button onClick={onClose}>
<XIcon aria-hidden="true" />
<span className="sr-only">Cerrar</span>
</button>aria-hidden="true" mantiene el SVG decorativo fuera del anuncio, y el span oculto proporciona el nombre. Una advertencia sobre aria-label: establece un nombre accesible en elementos interactivos y en cualquier cosa que lleve un rol explícito, y los navegadores frecuentemente lo ignoran en un <div> o <span> simple sin rol. Mantenlo en botones, enlaces, entradas y puntos de referencia etiquetados.
Leer el código fuente solo te lleva hasta cierto punto con cualquiera de esto. Activa VoiceOver con Cmd+F5 y escucha tu propia aplicación, y ejecuta axe DevTools en el navegador para detectar automáticamente etiquetas faltantes y controles sin nombre.
button cuando algo es clickable, y una verdadera label junto a cada entrada. Esos elementos vienen con soporte de teclado y un nombre que un lector de pantalla puede leer, todo gratis. Cuando algo cambia en la pantalla que una persona escuchando la página de otro modo extrañaría, pon una oración corta dentro de un div con aria-live="polite", y mantén ese div en la página desde el principio para que el cambio sea notado.
Lo siguiente: Más allá de los fundamentos, un mapa de qué viene después de los fundamentos.

