En 04-01 fijamos la regla de oro: usa el elemento HTML nativo antes que recrearlo. En 04-02 ya asomamos atributos como aria-describedby, aria-invalid y role="alert". Ahora toca entender qué son esos atributos: WAI-ARIA (Accessible Rich Internet Applications), un conjunto de atributos que permiten describir semántica —rol, estado y propiedades— en el marcado cuando el HTML nativo no llega. ARIA no cambia lo que ve el usuario ni añade comportamiento: solo cambia lo que expone el árbol de accesibilidad que estudiamos en el Módulo 2. Es una capa de información, no de interacción.
Precisamente por eso, esta lección trata ARIA como semántica declarativa: cómo etiquetar roles y estados en el HTML. Construir el widget interactivo —capturar teclado, mover el foco y actualizar estos estados con JavaScript— es el Módulo 5 (05-01). Aquí verás, por ejemplo, cómo darle a Marta el name/role/value correcto del desplegable de filtros de Cursalia; hacer que funcione con el teclado será el siguiente módulo.
Contenido
- Qué es ARIA y qué no es
- Las 5 reglas de ARIA
- Roles: landmark, widget y document
- Estados y propiedades
- El nombre accesible:
aria-label,aria-labelledby,aria-describedby aria-hiddeny el peligro de ocultar contenido focusable- Introducción a las live regions
- Caso Cursalia: name/role/value del desplegable de filtros
- Tabla de atributos ARIA frecuentes
- Errores comunes y consejos
- Ejercicios
- Conclusión
Qué es ARIA y qué no es
ARIA aporta tres tipos de información al árbol de accesibilidad:
- Roles (
role="..."): qué es un elemento (un botón, una pestaña, una alerta). - Propiedades (
aria-label,aria-describedby...): características relativamente estables. - Estados (
aria-expanded,aria-checked...): condiciones que cambian con la interacción.
Lo que ARIA NO hace, y conviene grabárselo:
- No añade comportamiento.
role="button"en un<div>no lo hace pulsable con teclado ni focusable; solo dice que es un botón. El comportamiento lo pones tú con JS (Módulo 5). - No cambia la apariencia. No aplica estilos.
- No mejora nada por sí solo. Un ARIA mal puesto es peor que no poner nada, porque miente al lector de pantalla.
"Ningún ARIA es mejor que un mal ARIA." Un
aria-*incorrecto crea una experiencia rota y difícil de diagnosticar.
Las 5 reglas de ARIA
La especificación resume el buen uso de ARIA en cinco reglas. Son la brújula de esta lección.
- Si puedes usar un elemento HTML nativo con la semántica y el comportamiento que necesitas, úsalo, y no uses ARIA. Un
<button>antes que un<div role="button">. Es la regla de oro de 04-01. - No cambies la semántica nativa salvo que sea imprescindible. No hagas
<h2 role="button">; envuelve el texto en un<button>dentro del<h2>. Cambiar el rol nativo borra el original (el "heading" desaparece). - Todo widget ARIA interactivo debe ser operable con el teclado. Si creas
role="button", debe recibir foco y activarse con Enter/Espacio. (La implementación es del Módulo 5, pero la regla nace aquí.) - No pongas
role="presentation"niaria-hidden="true"en elementos focusables. Ocultarías del árbol algo que el teclado sí puede alcanzar: el usuario enfoca "la nada". - Todo elemento interactivo debe tener un nombre accesible. Un botón sin nombre ("botón, sin etiqueta") es inservible para Marta.
graph TD
A["¿Necesito semántica extra?"] --> B{"¿Existe un elemento<br/>HTML nativo?"}
B -->|"Sí"| C["Úsalo. NO uses ARIA<br/>(Regla 1)"]
B -->|"No"| D["Usa ARIA:<br/>rol + estados + nombre"]
D --> E["Hazlo operable por teclado<br/>(Regla 3 → Módulo 5)"]
Roles: landmark, widget y document
Los roles ARIA se agrupan en familias. Tres importan aquí:
- Roles de landmark: regiones de navegación. Ya los obtienes gratis con HTML5 (
<nav>=navigation,<main>=main...). Solo usarías el ARIA equivalente (role="navigation") si no puedes emplear el elemento nativo. - Roles de widget: componentes interactivos (
button,checkbox,tab,menuitem,dialog,slider...). Describen controles ricos. Construir su comportamiento es el Módulo 5; aquí solo declaramos el rol. - Roles de documento/estructura: organizan contenido (
list,listitem,heading,article,alert,status...).
| Tipo de elemento HTML5 | Rol implícito (no hace falta declararlo) |
|---|---|
<nav> |
navigation |
<main> |
main |
<button> |
button |
<ul> / <li> |
list / listitem |
<h1>–<h6> |
heading (con su nivel) |
La lección: si el elemento nativo ya tiene el rol, no lo repitas con role. <nav role="navigation"> es redundante.
Estados y propiedades
Cuando el HTML nativo no puede expresar un estado (porque el control no existe de forma nativa), ARIA lo comunica. Los más frecuentes en marcado declarativo:
| Atributo | Qué comunica | Valores típicos |
|---|---|---|
aria-expanded |
Si un desplegable/acordeón está abierto | true / false |
aria-checked |
Estado de un checkbox/radio personalizado | true / false / mixed |
aria-selected |
Elemento seleccionado (pestañas, listas) | true / false |
aria-disabled |
Deshabilitado (pero aún perceptible por el lector) | true / false |
aria-current |
El elemento actual dentro de un conjunto | page, step, true... |
aria-pressed |
Estado de un botón de alternancia | true / false |
Un ejemplo declarativo: en la barra de navegación de Cursalia, marcar la página actual con aria-current="page" para que Marta sepa dónde está.
<!-- CORRECTO: aria-current señala la página activa -->
<nav aria-label="Principal">
<ul>
<li><a href="/cursos" aria-current="page">Catálogo</a></li>
<li><a href="/precios">Precios</a></li>
</ul>
</nav>Advertencia clave: estos estados son declaraciones. aria-expanded="false" en el botón de filtros solo dice "cerrado"; actualizarlo a true al abrir es trabajo de JavaScript (Módulo 5). Un estado que nunca se actualiza vuelve a mentir al lector.
El nombre accesible: aria-label, aria-labelledby, aria-describedby
El nombre accesible es el texto que el lector anuncia para identificar un elemento (el name de name/role/value). El navegador lo calcula siguiendo una prioridad. Simplificada, de mayor a menor:
aria-labelledby(apunta a losidde otros elementos que sirven de nombre). Gana sobre todo lo demás.aria-label(texto literal en el propio atributo).- El contenido/label nativo (el texto del
<button>, el<label for>asociado). - Otros mecanismos (
title...), como último recurso.
aria-label: da un nombre cuando no hay texto visible. Ideal para el botón de cerrar (una "X") o el buscador con solo una lupa.
<!-- CORRECTO: sin texto visible, aria-label da el nombre -->
<button type="button" aria-label="Cerrar el panel de filtros">✕</button>aria-labelledby: construye el nombre a partir de texto ya visible en la página (evita duplicar y traducir dos veces). Puede referenciar variosid, que se concatenan.
<!-- CORRECTO: el nombre de la región se toma de su encabezado visible -->
<section aria-labelledby="tit-filtros">
<h2 id="tit-filtros">Filtrar cursos</h2>
...
</section>aria-describedby: no aporta el nombre sino una descripción adicional (se lee después). Es lo que usamos en 04-02 para ayudas y errores de formulario.
Diferencia esencial: labelledby = cómo se llama; describedby = información extra. Y aria-labelledby sobrescribe al texto interno, así que úsalo con cuidado.
aria-hidden y el peligro de ocultar contenido focusable
aria-hidden="true" elimina un elemento (y sus descendientes) del árbol de accesibilidad, sin ocultarlo visualmente. Sirve para esconder del lector cosas puramente decorativas o duplicadas: un icono junto a un texto que ya lo dice.
<!-- CORRECTO: el icono es decorativo; el texto ya comunica la acción -->
<button type="button">
<span aria-hidden="true">🔍</span> Buscar
</button>El error grave (Regla 4) es poner aria-hidden="true" en algo focusable o que contenga elementos focusables (un enlace, un botón, un input). El resultado: Lucía tabula hasta un control que el lector considera inexistente. Enfoca "la nada".
<!-- INCORRECTO: se oculta del lector un enlace que sigue siendo focusable -->
<a href="/ayuda" aria-hidden="true">Ayuda</a>Si de verdad quieres retirar algo del todo (visual y del árbol), usa hidden, display:none o quítalo del DOM; aria-hidden solo afecta al árbol, no al foco.
Introducción a las live regions
Una live region es una zona cuyo contenido, al cambiar dinámicamente, el lector anuncia solo, sin que el usuario tenga que ir hasta ella. Es la base del criterio 4.1.3 (Mensajes de estado) que vimos como requisito en el Módulo 3. A nivel de marcado hay tres piezas:
aria-live="polite": anuncia cuando el lector termine lo que estaba diciendo. Para mensajes no urgentes: "Filtro aplicado: 12 cursos".aria-live="assertive": interrumpe y anuncia de inmediato. Para lo urgente: un error de envío.- Roles atajo:
role="status"equivale apolite;role="alert"equivale aassertive.
<!-- CORRECTO: región educada para confirmar resultados del filtro -->
<div role="status" aria-live="polite" id="resultado-filtro"></div>// El lector anunciará este texto en cuanto tu JS lo escriba:
document.getElementById('resultado-filtro').textContent =
'Filtro aplicado: 12 cursos encontrados.';Punto importante: el contenedor debe existir vacío en el DOM desde el principio; el lector vigila los cambios dentro de él. Nos quedamos en el marcado. El detalle del comportamiento dinámico, cuándo usar polite vs assertive en formularios validados en vivo y el testing corresponden a los Módulos 5, 6 y 7.
Caso Cursalia: name/role/value del desplegable de filtros
Cursalia tiene un botón "Filtros" que muestra u oculta un panel. Supongamos —siguiendo la regla 1— que no podemos usar <details>/<summary> nativos y hay que construirlo. Nuestra tarea en este módulo es solo el marcado semántico correcto para que Marta perciba qué es y en qué estado está:
<!-- Semántica declarativa: rol (button, nativo), nombre (texto) y estado (aria-expanded) -->
<button type="button"
aria-expanded="false"
aria-controls="panel-filtros">
Filtros
</button>
<div id="panel-filtros" hidden>
<h2>Filtrar cursos</h2>
<!-- checkboxes de categoría, nivel, idioma... -->
</div>Analicemos qué recibe Marta:
- Role:
button(nativo, no hace falta ARIA para el rol). - Name: "Filtros" (texto del botón).
- Value/State:
aria-expanded="false"→ el lector anuncia "contraído". aria-controls="panel-filtros": declara qué elemento gobierna el botón.
Con esto, Marta oye: "Filtros, botón contraído". Sabe qué es, cómo se llama y su estado.
Lo que falta y NO es de este módulo: que al pulsar se muestre el panel, que aria-expanded pase a true, que se retire el hidden, y que el teclado (Enter/Espacio/Escape) funcione. Todo ese comportamiento es 05-01/05-02. Aquí hemos dejado el andamiaje semántico perfecto para que ese JavaScript solo tenga que actualizar el estado.
Tabla de atributos ARIA frecuentes
| Atributo | Categoría | Para qué sirve |
|---|---|---|
role |
— | Declara qué es el elemento (si no hay nativo) |
aria-label |
Propiedad | Nombre por texto literal (sin texto visible) |
aria-labelledby |
Propiedad | Nombre tomado de otros elementos visibles |
aria-describedby |
Propiedad | Descripción adicional (ayudas, errores) |
aria-expanded |
Estado | Abierto/cerrado de desplegables y acordeones |
aria-controls |
Propiedad | Qué elemento controla este control |
aria-current |
Estado | Elemento actual (página, paso) |
aria-selected |
Estado | Elemento seleccionado (pestañas, opciones) |
aria-checked |
Estado | Marcado de checkbox/radio personalizado |
aria-disabled |
Estado | Deshabilitado pero perceptible |
aria-hidden |
Propiedad | Oculta del árbol (¡nunca en focusables!) |
aria-live |
Propiedad | Región que se anuncia al cambiar |
Errores Comunes y Consejos
- Reinventar con ARIA lo que ya existe.
<div role="button">en vez de<button>. Regla 1 siempre primero. - Sobrescribir la semántica nativa.
<a role="button">o<h2 role="tab">borran el rol original. Casi nunca es lo que quieres. - Estados que no se actualizan. Un
aria-expanded="false"que nunca cambia miente. Si pones el estado, algo (JS, Módulo 5) debe mantenerlo al día. aria-hiddensobre elementos focusables. Crea "trampas" donde el foco existe pero el lector no ve nada.- Elementos interactivos sin nombre. Un botón-icono sin
aria-labeles "botón sin etiqueta". Regla 5. - Confundir
labelledbycondescribedby. El primero es el nombre; el segundo, información extra que se lee después. - Live region creada en el momento del cambio. Debe existir vacía desde el inicio para que el lector la vigile.
- Consejo: valida tu ARIA con la vista de árbol de accesibilidad del navegador (DevTools) y con un lector real. Si dudas si añadir un
aria-*, probablemente la respuesta sea usar HTML nativo.
Ejercicios
Ejercicio 1: aplicar la regla 1
Un compañero ha maquetado el botón de reproducir de la vídeo-lección así. Reescríbelo cumpliendo las reglas de ARIA.
Ejercicio 2: dar nombre accesible
El botón de cerrar el panel de filtros solo muestra un icono "✕" y no tiene texto. Dótalo de un nombre accesible sin añadir texto visible, y explica qué mecanismo usas.
Ejercicio 3: name/role/value de un acordeón de FAQ
Cursalia tiene una pregunta frecuente plegable. Escribe solo el marcado semántico (rol, nombre y estado) del encabezado que abre/cierra la respuesta, indicando qué parte queda para el Módulo 5.
Soluciones
Solución 1. La regla 1 dice: usa el elemento nativo. Un <button> da rol, foco y teclado gratis, sin tabindex ni manejadores manuales de teclado; el icono se marca decorativo y se añade un nombre:
<button type="button" aria-label="Reproducir la lección" onclick="play()">
<span aria-hidden="true">▶</span>
</button>Así cumplimos las reglas 1 (nativo), 3 (operable con teclado, gratis) y 5 (tiene nombre).
Solución 2. Como no hay texto visible, se usa aria-label (propiedad que da un nombre por texto literal). El icono se oculta del lector porque es decorativo:
<button type="button" aria-label="Cerrar el panel de filtros">
<span aria-hidden="true">✕</span>
</button>Si el nombre estuviera ya escrito visiblemente en otro elemento, la opción preferente sería aria-labelledby apuntando a él.
Solución 3. El patrón accesible envuelve el texto de la pregunta en un <button> dentro del encabezado (no se cambia el rol del <h3>; regla 2):
<h3>
<button type="button" aria-expanded="false" aria-controls="resp-1">
¿Puedo pausar mi suscripción?
</button>
</h3>
<div id="resp-1" hidden>
<p>Sí, desde tu panel de alumno puedes pausarla en cualquier momento.</p>
</div>- Role:
button(nativo) dentro de unheadingque se conserva. - Name: el texto de la pregunta.
- State:
aria-expanded="false".
Lo que queda para 05-01: mostrar/ocultar #resp-1, alternar aria-expanded y gestionar el teclado. Aquí solo dejamos la semántica lista.
Conclusión
ARIA es la forma de añadir semántica —rol, estado y nombre— en el marcado cuando el HTML nativo no basta, pero su primera regla es no usarlo si hay una alternativa nativa, y un ARIA mal puesto es peor que ninguno. Has visto las cinco reglas, los tipos de rol, los estados declarativos como aria-expanded, cómo se calcula el nombre accesible (con la prioridad labelledby > label > texto nativo), el peligro de aria-hidden sobre focusables y una introducción a las live regions. Y has dejado el desplegable de filtros de Cursalia con un name/role/value impecable, listo para que el Módulo 5 le dé comportamiento.
Nos queda una pieza del principio "Perceptible" que aún no hemos implementado y que es puro CSS: que el contenido se vea y se lea bien. En 04-04 · Contraste de Color y Redimensionamiento de Texto cerraremos el módulo con ratios de contraste, no depender del color, unidades relativas, reflow y foco visible, aplicados a la paleta de marca de Cursalia y a Sofía con su zoom al 300%.
Curso de Accesibilidad Web
Módulo 1: Introducción a la Accesibilidad Web
- ¿Qué es la Accesibilidad Web?
- Importancia de la Accesibilidad Web
- Visión General de las Leyes y Normas de Accesibilidad
- Introducción a WCAG
Módulo 2: Comprensión de Discapacidades y Tecnologías de Asistencia
- Tipos de Discapacidades
- Visión General de Tecnologías de Asistencia
- Cómo las Personas con Discapacidades Usan la Web
Módulo 3: Principios del Diseño Accesible
- Perceptible: Hacer el Contenido Disponible a los Sentidos
- Operable: Interfaz de Usuario y Navegación
- Comprensible: Información y Operación
- Robusto: Compatibilidad con Tecnologías Actuales y Futuras
Módulo 4: Implementación de Accesibilidad en HTML y CSS
- HTML Semántico
- Formularios Accesibles
- Uso de Roles y Propiedades ARIA
- Contraste de Color y Redimensionamiento de Texto
Módulo 5: Accesibilidad en JavaScript y Multimedia
- Creación de Widgets de JavaScript Accesibles
- Accesibilidad del Teclado
- Contenido de Video y Audio Accesible
- Proporcionar Alternativas de Texto para Imágenes
Módulo 6: Pruebas y Evaluación de la Accesibilidad
- Técnicas de Pruebas Manuales
- Herramientas de Pruebas Automatizadas
- Pruebas de Usuario con Tecnologías de Asistencia
- Interpretación de Informes de Accesibilidad
