Consigna y alcance
Este es un problema de interacción de accesibilidad y de la Web Platform moderna. El objetivo es un menú, panel de filtros o superficie de ayuda que deje la página utilizable mientras admite light-dismiss. La HTML Popover API utiliza popover, popovertarget y el top layer para proporcionar un ciclo de vida de visualización gestionado por el navegador. No migres cada superposición a la API nativa; el posicionamiento en sí no es el ejercicio principal.
Lo que evalúa el entrevistador
- Distinguir
popover="auto",popover="manual"y la semántica de diálogos modales. - Mantener el activador, la superficie y las acciones de cierre semánticamente comprensibles.
- Manejar Escape, clics externos, múltiples superficies, retorno de foco y eventos del ciclo de vida.
- Explicar la mejora progresiva, el soporte de navegadores y un fallback sin fallas.
Aclaraciones que conviene hacer primero
Confirma si el fondo sigue siendo interactivo. Si debe bloquearse, utiliza <dialog> con showModal() en lugar de popover. Aclara si solo puede haber una superficie abierta, si pueden coexistir varias superficies manuales, si el activador alterna la apertura/cierre, si un envío exitoso del formulario la cierra y el rango mínimo de compatibilidad de navegadores. Una superficie de filtro suele ser no modal, pero los usuarios de teclado aún necesitan una ruta explícita de foco y de cierre.
Una respuesta de 30 segundos
Comienza con HTML semántico: coloca popovertarget en el activador y popover="auto" en la superficie; usa manual solo para la orquestación de cierre personalizada. El navegador proporciona el comportamiento de top layer, Escape y light-dismiss externo. Añado entrada de foco, retorno de foco, texto de estado y manejo del ciclo de vida con beforetoggle/toggle. Los navegadores no compatibles conservan el mismo DOM y nombres con un pequeño polyfill en lugar de un manejador de clics global que pueda cerrar superficies no relacionadas.
Solución paso a paso
1. Elegir popover o diálogo modal
Popover se adapta a menús, filtros, ayuda no bloqueante y acciones temporales; la página permanece no modal. <dialog> con showModal() se adapta a confirmaciones, pagos o advertencias que deben gestionarse primero porque proporciona semántica modal y bloquea el fondo. Ambos pueden usar el top layer, pero sus nombres accesibles, estrategia de foco y motivos de cierre difieren; reemplazar dialog con un atributo popover no es equivalente.
2. Establecer la relación entre el activador y la superficie
La estructura semántica más pequeña permite que el navegador mantenga la relación de destino:
<button type="button" popovertarget="filters" aria-controls="filters">
Filters
</button>
<div id="filters" popover="auto">
<form method="get">
<label>Status <select name="status"><option>All</option></select></label>
<button type="submit">Apply</button>
</form>
</div>popovertarget convierte al botón en un activador declarativo, y el navegador gestiona la relación durante la apertura y el cierre. aria-controls puede ayudar a la tecnología asistiva a comprender la relación, pero no reemplaza un nombre visible ni un comportamiento de foco correcto. Los componentes complejos deben retener una referencia al activador en el script y devolverle el foco solo si todavía existe y es enfocable.
3. Comprender auto, manual y light-dismiss
Un popover auto admite light-dismiss gestionado por el navegador: Escape o un clic afuera pueden cerrarlo, y participa en la cadena de cierre automático. Un popover manual no se cierra mediante esos mecanismos; llama a showPopover(), hidePopover() o togglePopover() explícitamente. Úsalo cuando varias superficies puedan coexistir o el ciclo de vida sea personalizado. No agregues un cierre por clic a nivel de documento sobre auto; las superficies anidadas y los clics del activador pueden entrar en conflicto.
4. Manejar el top layer, el posicionamiento y el backdrop
Una vez abierto, un popover entra en el top layer y ya no está limitado por el contexto de apilamiento overflow o z-index de un ancestro ordinario. Usa CSS Anchor Positioning o un diseño regular para la ubicación, con un fallback estático cuando la colocación falle. Un popover no modal no debería recibir automáticamente un backdrop de pantalla completa; si el producto necesita uno, vuelve a verificar si el requisito es en realidad el comportamiento de un diálogo modal. El top layer cambia el orden de pintado, pero no resuelve viewports estrechos, contenedores con scroll o la colocación ante colisiones.
5. Definir el foco y el comportamiento del teclado
Mueve el foco al primer control interactivo, o mantén el valor predeterminado del navegador solo después de probarlo. Con Escape, devuelve el foco al activador; si el activador fue eliminado, recurre al contexto visible más cercano. El orden de tabulación debe recorrer la superficie de forma natural; no ocultes todos los controles con tabindex="-1". Un popover tipo menú necesita la semántica de teclas de flecha y selección de elementos de menú, mientras que un formulario de filtro debe mantener el comportamiento de teclado de un formulario ordinario.
6. Observar el ciclo de vida en lugar de adivinar el estado global
Usa beforetoggle para validar una transición, registrar un motivo de cierre o sincronizar el estado de la aplicación; usa toggle después de que el estado realmente cambie para actualizar las etiquetas y el foco. Registra manejadores por elemento y límpialos al destruir el componente. Para superficies auto mutuamente excluyentes, confía en la pila de cierre del navegador en lugar de mantener un ID abierto global obsoleto.
7. Coordinar el envío del formulario y el estado de la aplicación
Después de un envío de filtro exitoso, llama a hidePopover() explícitamente si se desea, pero no trates el cierre como prueba de éxito. En una solicitud fallida, mantén la superficie abierta y asocia el error con el formulario. El estado abierto es un estado de UI temporal; los valores de filtro son estado de negocio. Mantenlos separados y vuelve a calcularlos ante cambios de navegación o historial en lugar de tratar a :popover-open como la única fuente de la verdad.
8. Usar mejora progresiva y probar la interacción
Detecta la disponibilidad de la API real, como HTMLElement.prototype.showPopover. Usa el comportamiento nativo cuando esté disponible; de lo contrario, preserva el mismo activador, contenido y nombres accesibles con un pequeño polyfill para la visualización, Escape, clics externos y retorno de foco. Prueba teclado, táctil, zoom, desplazamiento, superficies anidadas, activadores eliminados, formularios fallidos y movimiento reducido. Verifica los nombres, el estado y los errores con un lector de pantalla en lugar de revisar solo los píxeles.
Respuesta modelo
Definiría esto como un popover de filtro no modal, no como un diálogo. El botón usa popovertarget y el panel usa popover="auto"; el navegador gestiona el top layer, Escape y el light-dismiss externo, mientras que el script maneja el estado del formulario, el retorno de foco y los eventos del ciclo de vida. Al abrirse, el foco entra en el primer control; al cerrarse, regresa al activador. Un envío fallido mantiene la superficie abierta y expone el error; el éxito puede cerrarla. Usa manual solo cuando varias superficies personalizadas deban coexistir, y no dupliques el manejo de clics del documento para auto. Proporciona un fallback de posicionamiento estático y un polyfill semántico cuando falle la detección de características. Prueba las rutas de teclado, táctil, desplazamiento y tecnología asistiva.
Errores comunes
- Usar popover para una confirmación que debe bloquear el fondo, perdiendo la semántica modal.
- Agregar cierre por clic de documento en
auto, lo que puede cerrar superficies anidadas o controles de envío. - Alternar
displaysin mantener la relación del activador, el retorno de foco o el estado de error. - Simular un diálogo con un backdrop de pantalla completa mientras se mantiene un comportamiento no modal.
- Tratar el top layer como un posicionamiento automático e ignorar el desplazamiento, los viewports estrechos y el fallback ante colisiones.
- Cerrar un formulario fallido incondicionalmente para que los usuarios no puedan ver ni corregir el error.
- Probar únicamente clics de mouse y omitir el comportamiento con Escape, Tab, táctil y lectores de pantalla.
Preguntas de seguimiento
¿Cuándo deberías usar <dialog> en su lugar?
Usa <dialog> con showModal() cuando el fondo deba ser inerte, el usuario deba tomar una decisión primero o se requiera un comportamiento de foco modal. El light-dismiss y la semántica no modal de Popover no pueden reemplazar el bloqueo del fondo ni las acciones explícitas de cancelar/confirmar.
¿Cómo haces que varias superficies de filtro sean mutuamente excluyentes?
Prefiere auto para superficies mutuamente excluyentes para que el navegador gestione la cadena de cierre. Si pueden coexistir varias superficies manual, mantén un conjunto explícito de instancias, cierra solo la instancia de destino y almacena el activador y el retorno de foco de cada instancia de forma independiente.
¿Cómo das soporte a navegadores sin la Popover API?
Detecta la API y preserva el mismo DOM y atributos semánticos, con un polyfill para la visualización, el comportamiento de cierre, Escape, clics externos y retorno de foco. No degrades a un div posicionado absolutamente, sin nombre e inaccesible por teclado; documenta la matriz de compatibilidad y pruébala antes del lanzamiento.
¿Cómo pruebas los límites de light-dismiss?
Prueba clics en el activador, dentro de la superficie, en elementos hermanos externos, en contenedores con desplazamiento y en otro popover. Los clics internos deben permanecer abiertos; los clics externos deben cerrar solo las superficies auto elegibles, y los clics repetidos en el activador no deben crear un estado duplicado. Agrega pruebas de Escape y táctiles.
¿Puede la animación perjudicar la accesibilidad?
Usa prefers-reduced-motion para acortar o eliminar transiciones mientras mantienes la consistencia del foco y del estado legible. No ocultes los controles enfocables solo con opacidad; evita envíos duplicados durante el cierre y expón un cambio de estado estable a la tecnología asistiva.
¿Qué pasa si la colocación falla?
Con anchor positioning, proporciona un fallback de flujo normal o seguro para el viewport y responde a los cambios de tamaño y desplazamiento. Un fallo no debe recortar el contenido ni empujarlo fuera de la pantalla; el formulario principal sigue siendo utilizable sin la mejora de posicionamiento.