Lección 6 de 10 · 13 min de lectura · Revisado el 26/10/2026
Relaciones entre elementos: referencias por id y propiedades de relación
HTML expresa muchas relaciones por anidamiento (un <li> pertenece a la lista que lo contiene) o con atributos nativos (<label for>, headers en las tablas). Cuando la relación no se puede expresar así, porque el texto de ayuda está lejos del campo, la lista de sugerencias se pinta al final del documento o el resaltado se mueve por una lista sin que se mueva el foco real, ARIA ofrece propiedades de relación. En la lección sobre roles y estados viste cómo se compone un nombre con aria-labelledby; aquí repasamos el conjunto de relaciones, cómo funcionan las referencias por id en las que se apoyan y qué soporte real tiene cada una.
Cómo funcionan las referencias por id
Casi todas las propiedades de relación apuntan a otros elementos mediante su id. Unas admiten un solo identificador (aria-activedescendant, y en ARIA 1.2 también aria-errormessage y aria-details) y otras una lista separada por espacios (aria-labelledby, aria-describedby, aria-controls, aria-owns). Las reglas son sencillas, pero se incumplen constantemente:
- El identificador debe ser único en el documento. Si hay dos elementos con el mismo
id, el navegador usará uno de ellos (normalmente el primero) y el resultado dependerá del orden en que se pintó la página. - El destino debe existir cuando el navegador calcula la relación. Una referencia a un
idinexistente no da error visible: simplemente no hay nombre, ni descripción, ni relación. - Los identificadores distinguen mayúsculas y minúsculas:
ayuda-DNIyayuda-dnison distintos. - Las referencias no atraviesan el Shadow DOM. En un componente web con shadow root (el árbol encapsulado de un componente personalizado), un
aria-labelledbydel interior no puede apuntar a uniddel documento principal, ni al revés. Si trabajas con componentes web, el texto del nombre o de la descripción debe vivir dentro del mismo árbol que el control. - Los componentes reutilizables necesitan identificadores por instancia. Un campo que se pinta dos veces en la misma página con
id="ayuda"fijo rompe las dos relaciones. Genera identificadores únicos (en React, por ejemplo, conuseId).
Error frecuente
<!-- Componente «campo con ayuda» usado dos veces en el mismo formulario -->
<label for="campo">Teléfono</label>
<input id="campo" aria-describedby="ayuda">
<p id="ayuda">Nueve cifras, sin prefijo</p>
<label for="campo">Correo electrónico</label>
<input id="campo" aria-describedby="ayuda">
<p id="ayuda">Te enviaremos aquí el justificante</p>El segundo campo se queda sin etiqueta y recibe la ayuda del primero (mientras el primero puede acabar con las dos etiquetas como nombre); al pulsar la segunda etiqueta, el foco salta al primer campo. Visualmente todo parece correcto. Las herramientas automáticas detectan los id duplicados: es uno de los fallos que más rentabiliza pasarlas en cada versión.
Las propiedades de relación, una a una
| Propiedad | Para qué sirve | Soporte y cautelas |
|---|---|---|
aria-labelledby | Calcular el nombre a partir del texto de otros elementos | Muy bueno. Tiene prioridad sobre cualquier otra fuente de nombre |
aria-describedby | Asociar una descripción: formato esperado, ayuda, mensaje de error | Muy bueno. El texto se lee aplanado, sin estructura |
aria-controls | Indicar qué elemento modifica el control (botón y panel, combobox y lista) | Pocos lectores lo aprovechan (JAWS ofrece saltar al elemento controlado). Ponlo cuando la APG (se abre en una ventana nueva) lo indique, pero no transmitas con él nada imprescindible |
aria-owns | Declarar como hijos elementos que en el DOM están en otro sitio | Desigual entre navegadores. Casi siempre es mejor mover el elemento en el DOM |
aria-activedescendant | Señalar qué descendiente está activo mientras el foco real sigue en el contenedor o en el campo | Bueno en los patrones de la APG (combobox, listbox, rejilla). Exige gestionar a mano el resaltado visual y el desplazamiento |
aria-errormessage | Asociar el mensaje de error a un campo; solo se tiene en cuenta si el campo tiene aria-invalid="true" | Irregular. Combínalo con aria-describedby o usa solo este último |
aria-details | Enlazar con información extensa y estructurada (una nota, un comentario, la explicación de un gráfico) | Limitado. Ofrece además un enlace o un texto visible |
aria-flowto | Proponer un orden de lectura alternativo | Muy limitado. No lo uses para arreglar un orden del DOM incorrecto; corrige el orden (1.3.2) |
Punto clave
Las relaciones con mejor soporte son las que alimentan el nombre y la descripción (aria-labelledby y aria-describedby) y aria-activedescendant dentro de patrones conocidos. Para todo lo demás, la mejor relación es la que expresa el propio DOM: el elemento correcto, en el sitio correcto y en el orden correcto.
aria-activedescendant: el foco que no se mueve
En un buscador de municipios con autocompletado, la persona escribe en un campo de texto y, con las flechas, recorre las sugerencias. El foco real no puede salir del campo, porque entonces dejaría de poder escribir. La solución es aria-activedescendant: el campo conserva el foco y su atributo indica, mediante el id, qué opción está activa en cada momento. El lector anuncia esa opción como si tuviera el foco.
<label for="municipio">Municipio</label>
<input id="municipio" type="text" role="combobox"
aria-autocomplete="list" aria-expanded="true"
aria-controls="sugerencias" aria-activedescendant="mun-3">
<ul id="sugerencias" role="listbox" aria-label="Sugerencias">
<li id="mun-1" role="option">Cartagena</li>
<li id="mun-2" role="option">Lorca</li>
<li id="mun-3" role="option" aria-selected="true">Murcia</li>
</ul>Para que funcione, el código tiene cuatro obligaciones:
- Actualizar
aria-activedescendantcon cada flecha y vaciarlo (o quitarlo) cuando la lista se cierra. - Dar un
idúnico a cada opción y apuntar solo a elementos que existan y sean descendientes del elemento con foco o, en un combobox, opciones de la lista que controla. - Dibujar con CSS el resaltado de la opción activa, porque el navegador no lo hace: sin él se incumple 2.4.7 Foco visible para quien ve la pantalla.
- Desplazar la lista para que la opción activa quede a la vista.
La alternativa es el roving tabindex (mover el foco real entre los elementos, con tabindex="0" en el activo y -1 en el resto), que viste en el curso N3-C3. Usa aria-activedescendant cuando el foco deba quedarse en un campo de texto y el roving tabindex en los demás casos; los patrones completos se desarrollan en el curso N3-C5.
Posición y tamaño en conjuntos
El lector calcula «elemento 3 de 20» contando los elementos que hay en el DOM. Si la lista está paginada o virtualizada (solo existen en el DOM las filas visibles), esa cuenta es falsa. ARIA permite declarar la posición y el total reales:
aria-setsizeyaria-posinset: tamaño del conjunto y posición del elemento, en listas, opciones, pestañas, elementos de árbol o artículos de unfeed. Si el total es desconocido, ARIA 1.2 admitearia-setsize="-1".aria-level: nivel de anidamiento en árboles y en encabezados construidos conrole="heading".aria-rowcount,aria-rowindex,aria-colcountyaria-colindex: filas y columnas reales de una tabla o rejilla de la que solo se pinta una parte.
Ejemplo
El catálogo de trámites de una sede electrónica tiene 1.250 entradas y carga 20 cada vez. Sin más información, el lector anuncia «1 de 20» en el primer resultado de la tercera tanda. Con la posición real:
<ul aria-label="Trámites">
<li aria-posinset="41" aria-setsize="1250">…</li>
<li aria-posinset="42" aria-setsize="1250">…</li>
</ul>El soporte de estos atributos en listas simples varía entre lectores; pruébalo. Y no olvides el texto visible equivalente («Mostrando 41-60 de 1.250»), que ayuda a todo el mundo.
Descripciones: cuánto y qué
Que aria-describedby tenga buen soporte no significa que admita cualquier cosa. La descripción se lee después del nombre, el rol y el estado, aplanada en un único texto: sin encabezados, sin listas y sin enlaces operables. Por eso:
- Mantenla breve: formato esperado, una restricción, el mensaje de error.
- No apuntes a bloques largos (las condiciones de uso completas): se leerán enteros cada vez que el campo reciba el foco, y la persona no podrá navegar por ellos.
- No apuntes a contenido interactivo: el enlace que contenga se leerá como texto, pero no se podrá activar desde ahí.
- Si necesitas varias piezas (ayuda y error), usa una lista de identificadores en el orden en que quieres que se lean:
aria-describedby="error-dni ayuda-dni".
Pruébalo tú
Busca un formulario con textos de ayuda (el de alta en un servicio o el de una solicitud en una sede electrónica). Inspecciona un campo: ¿tiene aria-describedby?, ¿el panel Accesibilidad muestra una descripción? Copia el identificador y comprueba en la consola cuántos elementos lo tienen con document.querySelectorAll('[id="ayuda-dni"]').length, sustituyendo el valor. Si da 0, la referencia está rota; si da más de 1, está duplicada.
Consulta la definición formal de cada propiedad en la especificación, por ejemplo la de aria-activedescendant en WAI-ARIA 1.2.
Resumen
- Las relaciones ARIA se apoyan en referencias por
id: únicos, existentes, con las mayúsculas exactas y dentro del mismo árbol de Shadow DOM. aria-labelledby,aria-describedbyyaria-activedescendanttienen buen soporte;aria-controls,aria-owns,aria-details,aria-errormessageyaria-flowtolo tienen limitado o desigual.aria-activedescendantmantiene el foco en el campo y señala la opción activa, pero el resaltado visual y el desplazamiento son responsabilidad tuya.- En listas paginadas o virtualizadas,
aria-setsizeyaria-posinset(y sus equivalentes para filas y columnas) dan la posición real. - Las descripciones deben ser breves, sin contenido interactivo y en el orden de lectura deseado.
Comprueba lo que has aprendido
Esta lección se puede leer libremente. Para hacer su test de 3 preguntas, los ejercicios prácticos con páginas reales, guardar tu avance y optar al examen y al certificado, matricúlate en el curso.