La accesibilidad web se ha convertido en uno de los pilares fundamentales del desarrollo moderno con React y Next.js. La entrada en vigor de la Directiva Europea de Accesibilidad (EAA) en junio de 2025, junto con la consolidación de las WCAG 2.2, obliga a cualquier producto digital a ser usable por todas las personas, independientemente de sus capacidades. Crear interfaces inclusivas desde el primer momento no solo cumple con los requisitos legales, sino que mejora la experiencia para todos los usuarios, reduce costes de mantenimiento y refuerza la reputación de cualquier proyecto. En este artículo recorreremos los patrones, las herramientas y las buenas prácticas que permiten construir aplicaciones accesibles con React y Next.js, desde los fundamentos semánticos hasta las pruebas automatizadas.
Cuando hablamos de accesibilidad digital nos referimos a eliminar barreras que impiden a personas con discapacidad visual, auditiva, motora o cognitiva interactuar con la web. React ofrece una base excelente para cumplir con los estándares WCAG, pero muchas veces las malas prácticas con componentes personalizados rompen esa accesibilidad. Next.js, por su parte, incorpora características como el renderizado del lado del servidor y la optimización de imágenes que, bien utilizadas, potencian la inclusión. A lo largo de esta guía veremos cómo combinar el ecosistema de React con los patrones de ARIA, la semántica HTML y un proceso de testing continuo para garantizar experiencias universalmente accesibles.
Las Pautas de Accesibilidad para el Contenido Web (WCAG 2.2) establecen los criterios de éxito que toda aplicación debe cumplir para ser considerada accesible. Se organizan en cuatro principios clave: Perceptible, Operable, Comprensible y Robusto. Aplicarlos en un proyecto React implica mucho más que añadir atributos ARIA: significa diseñar componentes que desde su nacimiento respeten la estructura semántica, la navegación por teclado y la comunicación clara con las tecnologías de asistencia.
El estándar más extendido en entornos profesionales es el nivel AA, que incluye todas las exigencias del nivel A más requisitos adicionales como un contraste mínimo de 4.5:1 para el texto normal, indicadores de foco visibles y etiquetas descriptivas. Alcanzar este nivel no es un lujo técnico; en muchos países europeos es ya una obligación legal. Por ello, en React debemos traducir cada principio a decisiones concretas: desde la elección de la etiqueta HTML adecuada hasta la gestión programática del foco en componentes dinámicos.
El principio Perceptible nos recuerda que todo contenido debe presentarse de forma que los usuarios puedan percibirlo. En la práctica esto implica ofrecer alternativas textuales para imágenes, subtítulos para vídeos y un contraste de color suficiente. En React, componentes como las imágenes de Next.js deben incluir siempre el atributo alt descriptivo; los iconos decorativos deben ocultarse con aria-hidden, y los mensajes de error deben ser visibles y audibles para los lectores de pantalla.
El principio Operable exige que toda funcionalidad esté disponible desde el teclado. No basta con que un menú desplegable funcione al hacer clic; debe poder abrirse con Enter, cerrarse con Escape y navegarse con las flechas. React permite controlar estos eventos fácilmente, pero hay que planificar la gestión del foco para no dejar a los usuarios de teclado atrapados o perdidos.
Comprensible se refiere a la claridad del contenido y la predictibilidad de la interfaz. Los textos deben ser legibles, las etiquetas de los formularios claras y los mensajes de error deben ayudar al usuario a corregir sus datos. En React esto se traduce en asociar correctamente cada input con su label mediante htmlFor, usar aria-describedby para vincular mensajes de ayuda y mantener una navegación coherente.
Por último, Robusto busca la máxima compatibilidad con las tecnologías de asistencia actuales y futuras. Aquí es donde un uso correcto de ARIA marca la diferencia. Un componente de pestañas personalizado debe comunicar su rol, estado y relación con los paneles correspondientes. React facilita mantener estos atributos sincronizados con el estado del componente, pero exige disciplina para no abusar de roles innecesarios.
La regla de oro de la accesibilidad en React es sencilla: si existe un elemento HTML nativo que haga lo que necesitas, úsalo. Un botón debe ser un <button>, no un <div> con onClick. Lo mismo ocurre con los enlaces, los formularios o las tablas. Los navegadores y los lectores de pantalla entienden perfectamente la semántica de estos elementos y les asignan automáticamente el rol, el foco y el comportamiento esperado.
En React, componentes como Link de React Router renderizan un <a> real, lo que es correcto. El problema aparece cuando se construyen componentes personalizados con divs y spans y se intenta replicar esa semántica con ARIA. Aunque técnicamente es posible, casi siempre se olvida algún aspecto, como la respuesta a la tecla Espacio en un botón. Por eso, la primera decisión de accesibilidad es elegir el elemento HTML adecuado y solo añadir ARIA cuando no quede más remedio.
Una estructura de encabezados correcta transforma la experiencia de navegación para usuarios de lector de pantalla. Debe haber un único <h1> por página o ruta que describa el propósito principal. Los niveles siguientes no deben saltarse, creando una jerarquía lógica que permita a las personas moverse rápidamente entre secciones. En React, cuando se usan componentes como <section> o <article>, es imprescindible etiquetarlos con aria-labelledby apuntando a un encabezado visible.
Las listas también deben ser semánticas. Si el orden importa, se usa <ol>; si no, <ul>. Para pares de clave-valor existen las listas de definición <dl>. Evita usar listas solo para maquetación visual; para eso están CSS Grid y Flexbox. Un error común es reemplazar un <ul> por un <div> con role=»list». Aunque funciona, es más frágil y requiere más atributos; el elemento nativo transmite la semántica sin esfuerzo.
Los puntos de referencia (main, header, footer, nav, aside) también son cruciales. En React, la estructura de la aplicación debe incluirlos para que los usuarios puedan saltar directamente al contenido principal. Un enlace de tipo “Saltar al contenido” con la clase sr-only y focus:not-sr-only es una buena práctica que todos los proyectos deberían incluir, especialmente en aplicaciones con barras de navegación complejas.
La confusión entre enlaces y botones es una de las fuentes más comunes de problemas de accesibilidad. Un enlace (<a>) sirve para navegar a otra página o sección, mientras que un botón (<button>) ejecuta una acción dentro de la misma vista. En React, usar un div con onClick para navegar rompe esta semántica y deja a los usuarios de teclado sin la posibilidad de abrir el enlace en una pestaña nueva ni recibir el anuncio correcto del lector de pantalla. Siempre que necesites navegación, recurre a <a> o al componente Link de tu router.
El texto del enlace debe describir claramente su destino. Expresiones como “haz clic aquí” son inaccesibles fuera de contexto. Para enlaces repetidos, como “leer más” en una lista de artículos, se puede añadir un texto oculto visualmente pero disponible para el lector de pantalla: un <span className="sr-only"> con el título del artículo. Además, todos los enlaces deben tener un estilo de foco visible y responder a la tecla Enter.
Los botones nativos ya incluyen el rol, la gestión del foco y la respuesta a Enter y Espacio. Si por alguna razón técnica no puedes usar un <button>, entonces sí debes añadir role=»button», gestionar el tabindex y los eventos de teclado. Para botones que solo contienen un icono, es obligatorio proporcionar un nombre accesible mediante aria-label o texto oculto, de lo contrario el lector de pantalla anunciará algo sin sentido o directamente nada.
Los formularios son el punto de interacción más crítico en muchas aplicaciones. Cada campo debe estar asociado a una etiqueta visible mediante el atributo htmlFor (o for en JSX). React Hook Form facilita esta asociación, pero la responsabilidad de mantener la semántica es del desarrollador. Los grupos de opciones relacionadas, como radio buttons o checkboxes, deben ir envueltos en un <fieldset> con su correspondiente <legend>.
Los mensajes de error y ayuda deben vincularse al campo con aria-describedby. Así, cuando un lector de pantalla anuncie el campo, también leerá automáticamente las instrucciones o el error. Es importante que los errores no solo se muestren visualmente, sino que se anuncien de forma dinámica con aria-live=»polite» para que los usuarios que no ven la pantalla sepan que algo ha fallado. El atributo aria-invalid debe reflejar el estado real del campo en cada momento.
La navegación por teclado debe ser fluida: el orden de tabulación debe seguir un flujo lógico, normalmente el orden visual. Todos los campos deben ser accesibles sin ratón. Las validaciones en tiempo real no deben interrumpir la escritura con anuncios agresivos; para eso aria-live=»polite» espera a que el usuario termine una pausa antes de hablar. Combinando correctamente estas técnicas, los formularios en React pueden ser igual de eficaces para todos los usuarios.
ARIA (Accessible Rich Internet Applications) permite dotar de semántica a componentes que no tienen un equivalente HTML nativo. Sin embargo, la primera regla de ARIA es no usar ARIA. Siempre que exista un elemento HTML que resuelva la necesidad, debemos preferirlo. Solo cuando diseñemos componentes como modales, pestañas, acordeones o menús desplegables personalizados entraremos en el territorio de ARIA. En React, esos atributos se escriben en JSX con su nombre completo, por ejemplo aria-expanded, y deben actualizarse dinámicamente con el estado del componente.
El mayor riesgo con ARIA es la inconsistencia. Si un botón de menú tiene aria-expanded=»true» pero el menú está cerrado, el lector de pantalla transmitirá información falsa. Por eso, en React es vital que estos atributos sean derivados directamente del estado que controla la lógica visual. Nunca deben ser valores estáticos. Una buena práctica es centralizar la gestión de ARIA en hooks personalizados que devuelvan las props necesarias, asegurando así que el estado visual y el estado semántico estén siempre sincronizados.
Un modal accesible requiere varias capas de gestión. Debe tener role=»dialog» (o role=»alertdialog» si interrumpe al usuario) y un nombre accesible mediante aria-labelledby, normalmente apuntando al título del modal. Además, cuando se abre, el foco debe trasladarse al primer elemento interactivo del modal. Mientras esté abierto, el foco debe quedar atrapado dentro, impidiendo que el usuario tabule hacia el contenido de fondo. Al cerrarse, el foco debe regresar al elemento que lo disparó.
En React, librerías como Radix UI o Headless UI gestionan gran parte de esta complejidad, pero es importante entender qué hacen para poder personalizarlas sin romper la accesibilidad. Usan portales para renderizar el modal fuera del flujo normal del DOM y gestionan el foco programáticamente con useRef y useEffect. El contenido de fondo debe ocultarse con aria-hidden=»true» en el elemento raíz de la aplicación mientras el modal está activo, para que los lectores de pantalla lo ignoren por completo.
Además del foco, la interacción por teclado debe ser completa: la tecla Escape cierra el modal, Enter y Espacio activan los botones, y el tabulado circula solo entre los elementos del diálogo. No olvides añadir un botón visible para cerrar con un aria-label descriptivo, como “Cerrar diálogo”. Un modal bien implementado no solo cumple con las WCAG, sino que ofrece una experiencia mucho más profesional y robusta para todos los usuarios.
El patrón de pestañas (tabs) exige una estructura clara: un contenedor con role=»tablist», cada pestaña con role=»tab» y los paneles correspondientes con role=»tabpanel», vinculados mediante aria-controls y aria-labelledby. Solo una pestaña debe tener aria-selected=»true» en cada momento. La navegación por teclado debe seguir las recomendaciones de WAI-ARIA: las flechas izquierda y derecha mueven el foco entre pestañas, y Enter o Espacio (en activación manual) muestran el panel correspondiente.
En React, la activación manual es preferible cuando el contenido de los paneles es pesado o implica peticiones a API. La activación automática al mover el foco puede provocar saltos de layout que desorienten a los usuarios. En cualquier caso, los paneles inactivos deben permanecer ocultos con la propiedad hidden o con display:none, asegurándose de que no sean accesibles para el lector de pantalla ni para el foco. Una región aria-live puede anunciar cambios de pestaña de forma educada.
Los acordeones siguen una lógica similar: cada cabecera es un botón con aria-expanded y aria-controls. El panel asociado debe tener role=»region» y aria-labelledby apuntando a la cabecera. Cuando un panel se colapsa, aria-expanded pasa a false. De nuevo, el estado del atributo ARIA debe reflejar exactamente el estado visual del componente, algo que en React se consigue fácilmente vinculándolo a una variable de estado.
Las imágenes se clasifican en informativas, funcionales, decorativas y complejas. Las informativas necesitan un alt descriptivo y conciso que transmita la misma información que la imagen. Las funcionales (como un icono dentro de un enlace) deben describir la acción (“Ver carrito”, no “icono de carrito”). Las decorativas deben llevar alt vacío (alt=»») o role=»presentation» para que el lector de pantalla las ignore. Las imágenes complejas, como gráficos, requieren un alt breve más una descripción larga cercana, a menudo usando aria-describedby o un pie de figura.
En Next.js, el componente Image ofrece optimización automática, pero nunca añade el atributo alt por nosotros; es nuestra responsabilidad proporcionar un texto alternativo significativo. También es fundamental evitar imágenes de texto. Siempre que sea posible, el texto debe ser real y estilizado con CSS. Cuando el texto deba aparecer en una imagen por motivos de marca, el alt debe contener exactamente ese texto. El contraste de color en imágenes informativas también debe cumplir con las ratios mínimas.
Para probar imágenes, las herramientas automáticas como axe-core verifican la presencia del atributo alt, pero no su calidad. Por eso es esencial una revisión manual con lector de pantalla para escuchar cómo se anuncian. Un alt genérico como “imagen” o “foto” es tan inútil como la ausencia del atributo. Invierte tiempo en redactar buenos textos alternativos: beneficia al SEO y a la experiencia de todos los usuarios que, por cualquier motivo, no pueden ver la imagen.
Next.js incluye funcionalidades que, bien aprovechadas, mejoran la accesibilidad de forma casi transparente. El renderizado del lado del servidor (SSR) y la generación estática (SSG) producen HTML completo desde el primer momento, lo que garantiza que los lectores de pantalla y los motores de búsqueda reciban una estructura semántica completa, sin depender de JavaScript para mostrar el contenido. Esto es especialmente importante para usuarios con conexiones lentas o dispositivos menos potentes.
El componente Image de Next.js no solo optimiza el rendimiento, sino que facilita la inclusión de imágenes accesibles si se siguen las buenas prácticas de alt. El lazy loading nativo respeta la accesibilidad cuando las imágenes decorativas no interrumpen el flujo del contenido. Además, Next.js permite añadir metadatos y etiquetas de idioma de forma sencilla, lo que ayuda a los lectores de pantalla a cambiar correctamente la pronunciación. La estructura de enrutamiento basada en archivos también favorece una navegación predecible, uno de los pilares de la comprensibilidad.
Ninguna herramienta automática detecta todos los problemas de accesibilidad. Se estima que alrededor del 30-40% de los fallos pueden identificarse de forma automática. El resto requiere pruebas manuales con lectores de pantalla reales (NVDA, VoiceOver, JAWS), navegación solo con teclado y, en la medida de lo posible, sesiones con usuarios con discapacidad. Sin embargo, las pruebas automáticas son la primera línea de defensa y deben integrarse en el flujo de trabajo diario.
En el ecosistema React, la combinación de React Testing Library con jest-axe permite verificar que los componentes renderizados no violen las reglas de accesibilidad. React Testing Library fomenta consultas por rol (getByRole), lo que indirectamente garantiza que los elementos tengan la semántica correcta. Lighthouse CI puede añadirse al pipeline de integración continua para auditar cada despliegue. Para pruebas visuales de componentes aislados, Storybook dispone de un complemento de accesibilidad que ejecuta axe-core sobre cada historia.
Una estrategia completa incluye pruebas unitarias de accesibilidad en componentes críticos, auditorías automáticas en cada pull request, y un checklist manual que se revise antes de cada lanzamiento. Este checklist debe cubrir: navegación completa por teclado, orden de foco lógico, anuncios de lector de pantalla en estados dinámicos, contraste de color suficiente y presencia de alternativas textuales en todos los contenidos no textuales.
Para verificar el contraste de color, herramientas como el inspector de Chrome DevTools, el Contrast Checker de WebAIM o plugins de Figma como Stark permiten validar las combinaciones de color durante el diseño. En el código, eslint-plugin-jsx-a11y analiza el JSX en busca de errores comunes como imágenes sin alt, roles incorrectos o etiquetas de formulario mal asociadas. Axe DevTools, tanto en extensión de navegador como en línea de comandos (axe-cli), proporciona informes detallados de violaciones WCAG.
Para pruebas manuales, los lectores de pantalla son irremplazables: VoiceOver en macOS, NVDA en Windows y TalkBack en Android. Aprender a usar al menos uno de ellos, aunque sea de forma básica, cambia radicalmente la percepción de los problemas de accesibilidad. WAVE es otra extensión que ofrece un análisis visual muy intuitivo de los errores. La combinación de todas estas herramientas, junto con una cultura de equipo que valore la inclusión, marca la diferencia entre una aplicación que “pasa los tests” y una que realmente puede ser usada por cualquier persona.
Hacer una página web accesible significa asegurarse de que cualquier persona pueda utilizarla, tenga o no alguna discapacidad. Piensa en una rampa al lado de una escalera: no solo la usan personas en silla de ruedas, también padres con carritos de bebé o repartidores con cargas pesadas. En un sitio web, la accesibilidad se traduce en botones que se pueden pulsar sin ratón, imágenes con descripciones para quienes no las ven, y textos con suficiente contraste para que todos los lean sin esfuerzo. Al construir un proyecto con React y Next.js siguiendo estas pautas, los desarrolladores garantizan que ninguna persona quede excluida.
La accesibilidad no es un añadido complicado, sino una forma de trabajar que beneficia a todos. Un sitio accesible suele ser más claro, más rápido y mejor organizado. Además, las leyes europeas ya exigen que los servicios digitales sean accesibles desde 2025. Apostar por la inclusión digital no solo evita problemas legales, sino que demuestra un compromiso real con valores como la igualdad y la diversidad, lo que las personas usuarias y clientes valoran cada día más.
La implementación de accesibilidad en React y Next.js exige un enfoque multidisciplinar que abarca desde la elección del elemento HTML correcto hasta la integración de pruebas automatizadas en el CI/CD. La primera regla —no uses ARIA si puedes usar HTML nativo— reduce drásticamente la mayoría de los problemas. Cuando ARIA es inevitable, la sincronización entre el estado visual y los atributos de accesibilidad debe ser perfecta, apoyándose en hooks personalizados o en librerías probadas como Radix UI. Next.js potencia la semántica mediante SSR/SSG y un componente Image que debemos configurar con alt adecuados.
Las pruebas no pueden ser una ocurrencia tardía. jest-axe, React Testing Library con consultas por rol, Lighthouse CI y las auditorías manuales con lectores de pantalla forman un sistema de calidad que debe ser parte de la definición de “terminado”. Apuntar al nivel AA de WCAG 2.2 es el estándar de facto para aplicaciones empresariales y cumple con los requisitos legales de la EAA en Europa. La accesibilidad no es solo una cuestión de cumplimiento; es un indicador de madurez técnica, una mejora objetiva del SEO y una ventaja competitiva en un mercado que demanda cada vez más servicios digitales universales.
Crea interfaces impecables y eficientes con Alejandro Mejía. Especializado en React y NextJS, aseguramos diseños modernos y funcionales desde Madrid.