Addaw, ir a inicio Donar

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 id inexistente no da error visible: simplemente no hay nombre, ni descripción, ni relación.
  • Los identificadores distinguen mayúsculas y minúsculas: ayuda-DNI y ayuda-dni son distintos.
  • Las referencias no atraviesan el Shadow DOM. En un componente web con shadow root (el árbol encapsulado de un componente personalizado), un aria-labelledby del interior no puede apuntar a un id del 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, con useId).

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

PropiedadPara qué sirveSoporte y cautelas
aria-labelledbyCalcular el nombre a partir del texto de otros elementosMuy bueno. Tiene prioridad sobre cualquier otra fuente de nombre
aria-describedbyAsociar una descripción: formato esperado, ayuda, mensaje de errorMuy bueno. El texto se lee aplanado, sin estructura
aria-controlsIndicar 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-ownsDeclarar como hijos elementos que en el DOM están en otro sitioDesigual entre navegadores. Casi siempre es mejor mover el elemento en el DOM
aria-activedescendantSeñalar qué descendiente está activo mientras el foco real sigue en el contenedor o en el campoBueno en los patrones de la APG (combobox, listbox, rejilla). Exige gestionar a mano el resaltado visual y el desplazamiento
aria-errormessageAsociar 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-detailsEnlazar 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-flowtoProponer un orden de lectura alternativoMuy 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:

  1. Actualizar aria-activedescendant con cada flecha y vaciarlo (o quitarlo) cuando la lista se cierra.
  2. 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.
  3. 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.
  4. 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-setsize y aria-posinset: tamaño del conjunto y posición del elemento, en listas, opciones, pestañas, elementos de árbol o artículos de un feed. Si el total es desconocido, ARIA 1.2 admite aria-setsize="-1".
  • aria-level: nivel de anidamiento en árboles y en encabezados construidos con role="heading".
  • aria-rowcount, aria-rowindex, aria-colcount y aria-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-describedby y aria-activedescendant tienen buen soporte; aria-controls, aria-owns, aria-details, aria-errormessage y aria-flowto lo tienen limitado o desigual.
  • aria-activedescendant mantiene 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-setsize y aria-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.