Lección 7 de 10 · 11 min de lectura · Revisado el 26/10/2026
Skip links, gestión del foco en SPA, modales y contenido dinámico
En una página estática el foco se gestiona solo: la persona pulsa Tab y el navegador hace el resto. En cuanto entra JavaScript (contenido que aparece, paneles que se abren, vistas que cambian sin recargar), el desarrollador pasa a ser responsable de decidir dónde está el foco en cada momento. Si no lo decide, el foco se queda donde estaba o, peor, el navegador lo devuelve al principio del documento. Esta lección trata de esa responsabilidad en cuatro escenarios: los enlaces de salto, los diálogos modales, el contenido que aparece y desaparece, y los cambios de vista en aplicaciones de una sola página.
Enlaces de salto (skip links)
Un skip link es un enlace, normalmente el primer elemento enfocable de la página, que lleva directamente al contenido principal saltándose cabecera y menús. Es la técnica más sencilla para cumplir 2.4.1 Evitar bloques y beneficia a cualquier persona que use teclado, no solo a quien usa lector de pantalla (que además dispone de los landmarks).
Ejemplo
<body>
<a href="#contenido" class="skip-link">Saltar al contenido principal</a>
<header>…</header>
<main id="contenido" tabindex="-1">
<h1>…</h1>
</main>.skip-link {
position: absolute;
left: 0; top: 0;
transform: translateY(-150%);
background: #1a4fd6; color: #fff; padding: .75rem 1rem;
}
.skip-link:focus { transform: none; }El enlace puede estar oculto fuera de la pantalla hasta que recibe el foco, pero debe hacerse visible entonces: un enlace de salto que nunca se ve no sirve a quien navega con teclado sin lector. El tabindex="-1" en <main> garantiza que todos los navegadores mueven realmente el foco al destino y no solo desplazan la vista.
Diálogos modales
Un diálogo modal es la situación más exigente para el foco. Cuando se abre, deben pasar cinco cosas:
- El foco se mueve dentro del diálogo: al primer control interactivo, o al propio contenedor del diálogo si empieza por texto importante que hay que leer.
- El foco no puede salir del diálogo con Tab mientras esté abierto; al llegar al último control, vuelve al primero. Esto no es una trampa (2.1.2) porque existe una forma estándar de salir.
- Escape cierra el diálogo.
- El contenido de detrás queda inerte: no se puede enfocar ni leer con el lector de pantalla.
- Al cerrar, el foco vuelve al elemento que lo abrió.
El elemento nativo <dialog> con el método showModal() resuelve los puntos 2, 3 y 4 sin código adicional (el foco no puede llegar a la página de fondo, aunque desde el último control puede pasar un momento a la interfaz del navegador antes de volver al diálogo), y además coloca el diálogo en la capa superior, por encima de cualquier z-index. Es la opción recomendada hoy; todos los navegadores actuales lo soportan.
<button type="button" id="abrir">Eliminar expediente</button>
<dialog id="confirmar" aria-labelledby="conf-titulo">
<h2 id="conf-titulo">¿Eliminar el expediente 2024/0123?</h2>
<p>Esta acción no se puede deshacer.</p>
<button type="button" id="cancelar">Cancelar</button>
<button type="button" id="eliminar">Eliminar</button>
</dialog>const dlg = document.getElementById('confirmar');
const abrir = document.getElementById('abrir');
abrir.addEventListener('click', () => {
dlg.showModal();
document.getElementById('cancelar').focus(); // la opción segura primero
});
dlg.addEventListener('close', () => abrir.focus()); // devolver el focoSi no puedes usar <dialog>, el equivalente ARIA es un contenedor con role="dialog", aria-modal="true" y aria-labelledby, más JavaScript para el ciclo del foco, la tecla Escape y el atributo inert en el resto de la página mientras el diálogo esté abierto. inert es un atributo HTML que hace que un subárbol no sea enfocable ni perceptible por las tecnologías de apoyo; funciona en todos los navegadores actuales y sustituye al antiguo truco de poner aria-hidden="true" en todos los hermanos.
Error frecuente
Abrir el modal cambiando solo una clase CSS. El diálogo aparece visualmente, pero el foco sigue en el botón que lo abrió: la persona pulsa Tab y recorre el menú de la página de fondo sin saber que hay un diálogo abierto. Y al cerrarlo con la «X», el foco se pierde al principio del documento. Cada apertura y cada cierre deben mover el foco explícitamente.
Infografía · Ciclo
El ciclo del foco en un diálogo modal
Cinco fases que se repiten cada vez que se abre un diálogo; dialog con showModal() resuelve casi todas
- Fase 1: Abrir y mover el focoEl foco entra en el primer control o en el título del diálogo
- Fase 2: Retener el focoTab y Mayús+Tab circulan solo dentro del diálogo
- Fase 3: Fondo inerteLo de detrás no se enfoca ni se lee: inert o showModal()
- Fase 4: Cerrar con EscapeEscape, Cancelar o el botón de cerrar terminan el diálogo
- Fase 5: Devolver el focoEl foco vuelve al botón que abrió el diálogo
Tras la fase 5, el ciclo vuelve a empezar por la fase 1.
Leer la infografía como texto
Ciclo de cinco fases que se repite en cada apertura de un diálogo modal. Fase 1, abrir y mover el foco: el foco entra en el primer control o en el título del diálogo. Fase 2, retener el foco: Tab y Mayús+Tab circulan solo dentro del diálogo, sin que eso sea una trampa porque hay salida estándar. Fase 3, fondo inerte: lo que hay detrás no se puede enfocar ni leer, con el atributo inert o con showModal(). Fase 4, cerrar: Escape, el botón Cancelar o el botón de cerrar terminan el diálogo. Fase 5, devolver el foco: vuelve al botón que abrió el diálogo, y desde ahí el ciclo puede empezar de nuevo en la siguiente apertura.
Contenido que aparece y desaparece
No todo lo que aparece necesita recibir el foco. La regla es la intención de la persona:
- Si la persona ha pedido explícitamente abrir algo con lo que va a interactuar (un panel de filtros, un formulario inline, un menú), mueve el foco al principio de ese algo.
- Si lo que aparece es informativo y no requiere interacción (un mensaje «Guardado», un contador de resultados), no muevas el foco: anúncialo con una región activa (
role="status"). - Si el contenido aparece sin que la persona haya hecho nada (un aviso, un cambio automático), no muevas el foco nunca, porque sería un cambio de contexto no solicitado que desorienta (el criterio AAA 3.2.5 Cambios a petición lo exige expresamente).
- Si el elemento que tenía el foco se elimina del DOM (por ejemplo, al borrar una fila con su botón), mueve el foco a un elemento cercano con sentido: la fila siguiente, el encabezado de la lista o un mensaje de confirmación con
tabindex="-1". Si no lo haces, el navegador manda el foco al<body>.
Para los paneles que se despliegan en su sitio (acordeones, «mostrar más»), basta con aria-expanded en el botón: el contenido aparece justo después en el DOM y la persona llega a él con la siguiente pulsación de Tab o con las flechas del lector. No hay que mover el foco.
Cambios de vista en aplicaciones de una sola página
En una SPA (React, Vue, Angular, Svelte), pulsar un enlace del menú no recarga la página: el enrutador sustituye el contenido de <main>. Para el navegador no ha pasado nada: el foco sigue en el enlace del menú, el título de la pestaña no cambia y el lector de pantalla no anuncia nada. La persona que usa lector cree que el enlace no ha funcionado.
La solución tiene tres partes, que se aplican en cada cambio de ruta:
- Actualizar
document.titlecon el nombre de la nueva vista (2.4.2). - Mover el foco al principio de la nueva vista: al
<h1>o al contenedor<main>contabindex="-1". Así el lector anuncia el título y la siguiente pulsación de Tab empieza en el contenido nuevo. - Opcionalmente, anunciar el cambio en una región activa («Página cargada: Mis expedientes») para quien no esté atento al foco.
// Tras renderizar la nueva ruta
document.title = `${vista.titulo} - Sede electrónica`;
const h1 = document.querySelector('main h1');
h1.setAttribute('tabindex', '-1');
h1.focus();El curso de CSS, JavaScript y frameworks amplía estos detalles para React, Vue y Angular.
Punto clave
Cada vez que JavaScript cambia lo que hay en pantalla, pregúntate: ¿dónde está el foco ahora y dónde debería estar? Si la persona pidió el cambio, el foco va a lo nuevo; si lo cierra, vuelve a donde estaba; si es solo informativo, no se mueve y se anuncia.
Resumen
- Un enlace de salto visible al recibir el foco, apuntando a
<main tabindex="-1">, cumple 2.4.1 de forma sencilla. - Un modal mueve el foco dentro, lo retiene, se cierra con Escape, deja el fondo inerte y devuelve el foco al abrir;
<dialog>conshowModal()resuelve casi todo. - El contenido solicitado recibe el foco; el informativo se anuncia con una región activa; lo no solicitado nunca mueve el foco.
- Si eliminas el elemento enfocado, lleva el foco a un vecino con sentido.
- En cada cambio de ruta de una SPA: actualizar
document.title, enfocar el<h1>y, si procede, anunciar.
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.