Normativas · ARIA

Estado Estado de región dinámica

aria-live

Marca una región cuyas actualizaciones deben ser anunciadas por la tecnología de apoyo sin desplazar el foco. Se recomienda «polite» para la mayoría de los casos y «assertive» únicamente para actualizaciones genuinamente urgentes. La región debe estar en el DOM desde la carga inicial.

Cuándo utilizarlo

Cuando alguna parte de la página se actualiza de forma asíncrona y se desea que los usuarios de lectores de pantalla estén informados sin interrumpir lo que están leyendo en ese momento. Los casos habituales son: resúmenes de validación de formularios, recuentos de resultados de búsqueda, notificaciones tipo toast, mensajes de chat en tiempo real y contadores del carrito de compra.

El nivel de cortesía debe elegirse deliberadamente:

  • aria-live="polite" — espera a que el usuario esté inactivo antes de anunciar. Se debe usar para casi todo. Mensajes de estado, resultados cargados, ítem añadido al carrito.
  • aria-live="assertive" — interrumpe al usuario de inmediato. Se reserva para información genuinamente urgente: sesión que expira en 30 segundos, error en el envío del formulario, pago rechazado. El abuso de este valor hace que la página resulte hostil.
  • aria-live="off" (valor predeterminado) — sin anuncios.

Los roles nativos role="status" (implica polite) y role="alert" (implica assertive) incluyen aria-live con valores predeterminados adecuados. Se recomienda usarlos cuando encajen; se debe recurrir a aria-live en un contenedor personalizado cuando no sea así.

Cómo mantenerlo sincronizado

La regla fundamental: la región en tiempo real debe estar en el DOM desde la carga inicial. Los navegadores y las tecnologías de apoyo configuran la «vigilancia» de la región cuando esta aparece por primera vez en el árbol de accesibilidad. Si se crea la región y se inyecta contenido en ella en el mismo ciclo de JavaScript, el anuncio suele perderse.

El patrón correcto es:

<div id="status" aria-live="polite"></div>

Se renderiza el contenedor vacío al cargar la página. Posteriormente, se escribe texto en él mediante JavaScript. El lector de pantalla anuncia el cambio.

Otras reglas:

  • Se debe actualizar estableciendo textContent; reemplazar el HTML externo completo de la región puede interrumpir la vigilancia.
  • Los anuncios repetidos requieren un cambio de contenido: escribir la misma cadena dos veces a menudo no produce un segundo anuncio. Se puede añadir un contador, una marca de tiempo o limpiar brevemente la región.
  • Se debe combinar con aria-busy="true" durante actualizaciones en varios pasos para evitar anuncios parciales.
  • Se debe combinar con aria-atomic para controlar si se anuncia solo el diferencial o toda la región.

Errores frecuentes

  • Crear la región en tiempo real en el mismo ciclo que el contenido: no se produce ningún anuncio.
  • Usar aria-live="assertive" para todo. Los usuarios silencian la pestaña.
  • Establecer aria-live en un control interactivo. Las regiones en tiempo real son para actualizaciones de estado, no para widgets interactivos.
  • Ocultar la región en tiempo real con display: none. Las regiones ocultas mediante CSS también quedan ocultas en el árbol de accesibilidad y no producen ningún anuncio; se debe usar la técnica de ocultación visual (clip / sr-only) en su lugar.
  • Inyectar contenido muy extenso (párrafos de texto) en una región en tiempo real de una sola vez: el usuario no puede recorrerlo.
  • Olvidar limpiar la región tras la lectura del mensaje, de modo que las actualizaciones idénticas posteriores no producen ningún anuncio.

Ejemplo

<form>
  <label for="zip">Código postal</label>
  <input id="zip" name="zip" />
  <button type="submit">Buscar</button>
</form>

<!-- Siempre presente desde la carga inicial -->
<div id="lookup-status" aria-live="polite" class="sr-only"></div>

<script>
  document.querySelector('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const status = document.getElementById('lookup-status');
    status.textContent = 'Buscando ubicación…';
    const place = await lookup(document.getElementById('zip').value);
    status.textContent = `Ubicación: ${place}`;
  });
</script>