Focus management en flujos complejos
Focus management en flujos complejos
8 reglasAl abrir un modal, mueve el foco dentro y atrapalo hasta que se cierre
Cuando un dialogo modal se abre, el foco del teclado debe moverse de inmediato a un elemento dentro del dialogo, según el WAI-ARIA APG Dialog Pattern. Mientras el modal este abierto, Tab y Shift+Tab deben ciclar unicamente dentro de el: al llegar al último elemento, Tab vuelve al primero, y viceversa. Ningun elemento del fondo debe ser alcanzable con teclado. WCAG 2.1.2 prohibe las trampas de foco, pero exime explicitamente a los modales siempre que el usuario pueda salir con Escape u otro método conocido.
WAI-ARIA APG Dialog (Modal) Pattern · WCAG 2.1.2 No Keyboard Trap (Nivel A)Al cerrar un modal, devuelve el foco al elemento que lo disparo
Cuando un modal se cierra, el foco debe volver al elemento que lo abrio. Esto preserva el contexto espacial del usuario de teclado: sabe exactamente donde estaba en la página. El patrón es guardar el disparador antes de abrir (const trigger = document.activeElement) y al cerrar invocar trigger.focus(). El APG lo norma verbatim: el foco regresa al elemento que invoco el dialogo salvo que ya no exista o que el flujo sugiera un destino mejor. Si dejas el foco en body, el usuario reinicia la navegación desde el principio de la página.
En SPAs, mueve el foco al heading de la vista y anuncia el cambio de ruta
Las Single Page Applications no disparan el comportamiento nativo del navegador al cambiar de ruta: ni scroll al inicio ni anuncio por lector de pantalla. El desarrollador debe gestionarlo a mano. El destino recomendado es el primer heading único de la nueva vista (con tabindex="-1" para poder enfocarlo); si no existe, el elemento que provea más contexto. Además document.title debe actualizarse para que el lector anuncie la página. Si el foco se queda en el link de navegación, el usuario de teclado no sabe que el contenido cambio.
Contenido dinámico: mueve el foco o usa live region según urgencia
Al insertar contenido nuevo en el DOM hay dos estrategias validas: mover el foco al contenido (cuando requiere acción o atención inmediata, como un resumen de errores o el siguiente paso de un wizard) o usar una region aria-live (cuando es informativo y no debe interrumpir). Nunca muevas el foco a un spinner de carga: anuncia el estado con ARIA y redirige el foco al contenido final cuando llegue. Evita combinar foco y aria-live en el mismo elemento, porque produce lecturas duplicadas. La elección depende de si el usuario debe actuar ya o solo enterarse.
Nunca muevas el foco ni cambies de contexto solo por recibir foco
Recibir foco en un elemento no debe, por si solo, desencadenar un cambio de contexto: no abrir ventanas nuevas, no enviar formularios, no redirigir la URL ni mover el foco a otro componente. WCAG 3.2.1 On Focus (Nivel A) lo prohibe de forma absoluta y cualitativa: cero cambios de contexto disparados unicamente por el foco. Estos saltos inesperados desorientan a usuarios de teclado, lectores de pantalla y personas con discapacidades cognitivas. El cambio solo debe ocurrir tras una acción explicita: click, Enter o activación del control.
WCAG 3.2.1 On Focus (Nivel A)El orden de tabulación sigue el flujo lógico del DOM y el visual
Los componentes focusables deben recibir foco en un orden que preserve significado y operabilidad: el orden de lectura del DOM debe coincidir con el orden visual percibido. WCAG 2.4.3 Focus Order (Nivel A) lo exige cuando la secuencia de navegación afecta el significado. El antipatron documentado es usar tabindex con valores positivos para reordenar arbitrariamente, lo que hace que el foco salte entre secciones no relacionadas. Si necesitas un orden visual distinto al del código, reordena el DOM o usa CSS de layout, no tabindex positivo.
El foco debe ser visible y no quedar tapado por headers sticky
Todo elemento que recibe foco de teclado debe mostrar un indicador visual claro (WCAG 2.4.7 Focus Visible, Nivel AA). Además, cuando el elemento enfocado queda bajo un header o footer sticky, WCAG 2.4.11 Focus Not Obscured (Nivel AA, WCAG 2.2) exige que no quede completamente oculto por contenido del autor; los sticky headers son el caso típico de fallo (Failure F110). La técnica documentada es html { scroll-padding-top: 80px }, que hace que el navegador deje margen sobre el elemento enfocado al hacer scroll. Nunca elimines el indicador de foco con outline:none sin reemplazo.
Usa inert en el fondo del modal y skip links en flujos largos
El atributo HTML inert aplicado al contenido detras de un modal lo vuelve completamente no interactivo de forma nativa: bloquea clicks, foco y eventos de foco, deshabilita la selección de texto, lo saca del find-in-page y lo oculta del arbol de accesibilidad. Es Baseline Widely Available desde abril 2023, lo que lo hace más robusto que las librerias de focus-trap en JS. Un modal con solo aria-modal="true" sin inert deja el fondo accesible para algunos lectores en modo browse. En wizards y flujos largos, complementa con skip links y landmarks ARIA para no recorrer todos los controles (WCAG 2.4.1 Bypass Blocks).
- R-245 Al abrir un modal, mueve el foco dentro y atrapalo hasta que se cierre
- R-246 Al cerrar un modal, devuelve el foco al elemento que lo disparo
- R-247 En SPAs, mueve el foco al heading de la vista y anuncia el cambio de ruta
- R-248 Contenido dinámico: mueve el foco o usa live region según urgencia
- R-249 Nunca muevas el foco ni cambies de contexto solo por recibir foco
- R-250 El orden de tabulación sigue el flujo lógico del DOM y el visual
- R-251 El foco debe ser visible y no quedar tapado por headers sticky
- R-252 Usa inert en el fondo del modal y skip links en flujos largos