React Accesible
Toda aplicación React se renderiza al mismo HTML que siempre ha enviado el navegador, y cada herramienta de asistencia funciona a partir del DOM que producen tus componentes. La accesibilidad en React es principalmente una serie de pequeñas decisiones 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 conservan sus guiones: aria-live, aria-label, y un simple role.
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. Dispara su manejador de clic en Enter y en Espacio. Un lector de pantalla lo anuncia como botón y lee su texto como el nombre, que también es el nombre que identifica el software de control de voz. El navegador maneja el estado deshabilitado, el anillo de enfoque y el estilo activo.
// el navegador te da enfoque, activación por teclado y el anuncio "botón"
<button className="die" onClick={hold}>{value}</button>Un <div> con un manejador 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 una serie de texto sin indicación de que algo sucederá si interactúas con él.
El parche usual es role="button" más tabIndex={0}, que pone el elemento en el orden de tabulación y cambia lo que se anuncia. El comportamiento sigue faltando. Tendrías que agregar un manejador onKeyDown, verificar Enter y Espacio, llamar a preventDefault() en Espacio para que la página deje de desplazarse, y luego mantener un estado deshabilitado hecho a mano sincronizado con el estilo. Es bastante código para reconstruir algo que el navegador ya envía. Usar el verdadero <button> es el camino más corto, y se mantiene correcto conforme 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 entre 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 es elegir el elemento que ya hace el trabajo.
Anunciando lo que 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 transmite esa información: un contenedor que el lector de pantalla observa y anuncia siempre que su contenido cambia. La clase sr-only a continuación lo oculta visualmente, usando un patrón CSS que se cubrirá más adelante en este capítulo.
<div aria-live="polite" className="sr-only">
{isGameWon && <p>¡Ganaste! Presiona Nuevo Juego para comenzar de nuevo.</p>}
</div>El envoltorio se renderiza cada vez, vacío al principio, y React intercambia un párrafo 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. Monta la región y su texto juntos en un solo renderizado y muchos lectores de pantalla no anuncian nada en absoluto: 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" pone 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 llegar un momento después del cambio visual. Ese retraso es deliberado, y polite es la configuración correcta para casi todo.
Interacción del 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 renderiza tu JSX es la secuencia por la que la gente se mueve. Reordenar visualmente con CSS deja un orden de tabulación que salta por toda la pantalla, y los valores tabIndex positivos 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 necesita un destino de enfoque como un encabezado de diálogo.
Dos reglas más. Mantén el enfoque visible: evita outline: none a menos que un estilo :focus-visible de tu parte lo reemplace. Y mantén una salida disponible: una modal que deliberadamente mantiene el enfoque dentro de sí misma necesita Escape para cerrar y necesita devolver el enfoque a su disparador.
Moviendo el enfoque deliberadamente
Cuando la interfaz cambia de forma, el enfoque puede terminar en ningún lado. Alguien activa un botón, el botón se elimina o reemplaza, y el enfoque 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 enfoque a algún 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 efecto se ejecuta después de que React ha comprometido ese nodo en la pantalla, así que el elemento está ahí para recibir el enfoque. Proteger con isGameWon lo evita robar enfoque en cada renderizado.
El mismo patrón cubre los otros momentos comunes: una modal toma enfoque al abrirse y lo devuelve al disparador al cerrar, una validación fallida envía el enfoque al primer campo inválido, eliminar una fila mueve el enfoque a la fila que la reemplazó. La regla debajo es una línea: si tu código eliminó la cosa que tenía enfoque, tu código decide dónde va el enfoque después.
Texto visualmente oculto
Mucho estado es obvio desde el diseño y silencioso para un lector de pantalla: una marca verde junto a un campo, un dado que se ve presionado, un número que se lee claramente desde donde está. El texto visualmente oculto deletrea eso para cualquiera que esté escuchando la página.
La convención es una clase llamada sr-only. No tiene significado para React o para el navegador: es un nombre de clase simple, y estas reglas CSS son lo que hace 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 permanece en el árbol de accesibilidad mientras no ocupa espacio visual. display: none y visibility: hidden lo quitarían de ese árbol también, ocultándolo de todos.
El botón solo con icono 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 simple <div> o <span> sin rol. Mantenlo en botones, enlaces, entradas y puntos de referencia etiquetados.
Leer el código solo te llevará 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 atrapar etiquetas faltantes y controles sin nombre automáticamente.
button cuando algo es clickeable, 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 otra manera perderí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 se note. Lo siguiente: Más allá de lo básico, un mapa de lo que viene después de los fundamentos.

