Popover en HTML: menús, avisos y globos sin JavaScript
En esta lección
En la lección de details y dialog viste el atributo popover en cuatro líneas. Aquí lo vemos entero: cómo se comporta, qué hace el navegador por ti, cómo darle estilo y animarlo, cómo controlarlo desde JavaScript y cómo usarlo para dos cosas muy comunes en una web: menús desplegables y avisos temporales. Un popover es una capa flotante que aparece encima del resto de la página y que no obliga a la persona a responder.
Lo mínimo: un botón y una capa
Un popover necesita dos piezas: el elemento con el atributo popover y un botón que lo señale con popovertarget, cuyo valor es el id del popover:
<button type="button" popovertarget="normas">Normas de la sala</button>
<div id="normas" popover>
<h2>Normas</h2>
<p>Deja el material en su sitio y avisa si ves una presa floja.</p>
</div>
No hace falta escribir ni una línea de JavaScript. El navegador se encarga de:
- Mantener la capa oculta hasta que se pulse el botón.
- Colocarla por encima de todo lo demás, sin que tengas que tocar
z-index. - Cerrarla con la tecla Esc (en el modo por defecto).
- Devolver el foco al botón al cerrarla.
- Informar a los lectores de pantalla de que el botón controla una capa y si está abierta o cerrada.
Cualquier elemento puede ser un popover: un <div>, un <nav>, un <ul>, un <p>. Elige el que tenga el significado correcto para su contenido.
Qué pasa con el aspecto por defecto
Cuando se abre, el navegador le pone unos estilos por defecto: se coloca centrado en la ventana (como un modal pequeño), con borde, relleno y fondo blanco. Es un punto de partida razonable, pero para un menú o un globo querrás cambiarlo. Más abajo verás cómo.
Los tipos de popover: auto y manual
El valor del atributo decide cómo se cierra:
| Valor | Se cierra al… | Otros popovers abiertos |
|---|---|---|
popover o popover="auto" | Pulsar fuera, pulsar Esc o pulsar otra vez el botón | Abrirlo cierra los demás (salvo sus padres) |
popover="manual" | Pulsar su botón de cerrar o con JavaScript | No afecta a los demás |
El comportamiento de auto se llama light dismiss (cierre ligero): la persona no tiene que buscar el botón de cerrar, basta con hacer clic en cualquier otro sitio. Es lo que quieres en un menú o en un globo de ayuda.
manual es para cosas que deben quedarse hasta que alguien decida cerrarlas: un aviso de “Ruta guardada”, una barra de progreso flotante, un panel de chat. Como no se cierra solo, tú te ocupas de ofrecer una forma de cerrarlo.
Nota: existe un tercer valor reciente,
popover="hint", pensado para globos de ayuda que aparecen al pasar el ratón y que no cierran los popoversautoque ya estén abiertos. Es nuevo y no todos los navegadores lo admiten, así que de momento quédate conautoymanual.
Qué hace cada botón: popovertargetaction
Un botón con popovertarget hace por defecto toggle: abre si está cerrado y cierra si está abierto. Con popovertargetaction eliges otra cosa:
<button type="button" popovertarget="panel" popovertargetaction="show">Abrir</button>
<button type="button" popovertarget="panel" popovertargetaction="hide">Cerrar</button>
<button type="button" popovertarget="panel">Alternar</button>
Lo habitual es un botón toggle para abrir y, dentro del propio popover manual, un botón hide para cerrarlo:
<div id="aviso" popover="manual" role="status">
Ruta guardada
<button type="button" popovertarget="aviso" popovertargetaction="hide"
aria-label="Cerrar aviso">x</button>
</div>
Un botón puede estar dentro del popover al que apunta y seguir funcionando. Y fíjate en type="button": dentro de un formulario, un <button> sin type lo enviaría.
Estilo: el popover es un elemento más
Como es un elemento normal, le das estilo con tu CSS. Pero hay dos detalles propios.
La pseudoclase :popover-open
Representa un popover que está abierto ahora mismo. Sirve para cambiar su aspecto solo en ese estado:
[popover] {
border: 1px solid #94a3b8;
border-radius: 8px;
padding: 12px 16px;
}
[popover]:popover-open {
box-shadow: 0 8px 24px rgb(0 0 0 / 0.2);
}
Cuando está cerrado, el navegador lo oculta con display: none. No lo pises con un display: flex en el selector [popover], porque lo dejarías siempre visible. Si necesitas display: flex, ponlo en [popover]:popover-open.
Colocarlo donde quieras
Por defecto aparece centrado en la ventana. Para colocarlo bajo un botón, por ejemplo, quítale el margen automático y dale una posición:
#menu-usuario {
margin: 0;
position: absolute;
inset: 56px auto auto 12px; /* arriba, derecha, abajo, izquierda */
}
Calcular esas coordenadas a mano es incómodo, porque dependen de dónde esté el botón. Existe una forma de pegar el popover a su botón de manera automática, que verás en Posicionar con anchor.
El fondo: ::backdrop
Los popovers también tienen ::backdrop, igual que un <dialog> modal. Es una capa que cubre la página detrás del popover y que por defecto es transparente:
#ayuda::backdrop {
background: rgb(0 0 0 / 0.3);
}
Úsalo solo si el popover debe llamar mucho la atención. Un menú desplegable con el resto de la página oscurecida resulta raro.
Animar la apertura y el cierre
Como un popover pasa de display: none a visible, una transición normal no se ve. Hoy se resuelve con tres piezas: @starting-style (el estado de partida al aparecer), y allow-discrete para que display y overlay también se transicionen:
[popover] {
opacity: 0;
transition:
opacity 0.2s,
display 0.2s allow-discrete,
overlay 0.2s allow-discrete;
}
[popover]:popover-open {
opacity: 1;
}
@starting-style {
[popover]:popover-open {
opacity: 0;
}
}
La propiedad overlay mantiene el elemento en la capa superior mientras dura la animación de cierre. Es una técnica reciente: en un navegador que no la entienda, el popover sencillamente aparece y desaparece sin animación, y sigue funcionando. Las transiciones en general las tienes en Transiciones.
El evento toggle
Cada vez que un popover se abre o se cierra, dispara el evento toggle (y, un instante antes, beforetoggle, que se puede usar para preparar el contenido). El evento trae dos propiedades: oldState y newState, con el valor "open" o "closed":
const menu = document.querySelector('#menu-usuario');
menu.addEventListener('toggle', (evento) => {
if (evento.newState === 'open') {
console.log('Menú abierto');
} else {
console.log('Menú cerrado');
}
});
Es útil para cargar datos cuando se abre, para cambiar el texto de un botón o para registrar qué paneles usa la gente. Los eventos en general los tienes en eventos en JavaScript.
Controlarlo desde JavaScript
A veces quien abre el popover no es un botón, sino tu código: una petición que termina, un temporizador, una acción que se completa. Hay tres métodos:
| Método | Qué hace |
|---|---|
elemento.showPopover() | Lo abre |
elemento.hidePopover() | Lo cierra |
elemento.togglePopover() | Lo alterna; devuelve true si queda abierto |
Un aviso que se muestra al guardar y desaparece solo a los tres segundos:
const aviso = document.querySelector('#aviso');
function avisar() {
aviso.showPopover();
setTimeout(() => aviso.hidePopover(), 3000);
}
Para saber si está abierto, usa elemento.matches(':popover-open'). Si llamas a showPopover() sobre uno que ya está abierto, o a hidePopover() sobre uno cerrado, el navegador lanza un error en algunos casos, así que comprueba el estado antes cuando no estés seguro.
Cuidado: no confundas el atributo con la propiedad. El atributo HTML es
popover; en JavaScript,elemento.popoverdevuelve"auto","manual"onull. Un elemento sin el atributo no puede abrirse conshowPopover(): lanza un error.
Dos usos típicos
Un menú desplegable
Un menú de cuenta es un <nav> (o una lista) con enlaces, abierto desde un botón:
<button type="button" popovertarget="menu-usuario">Mi cuenta</button>
<nav id="menu-usuario" popover aria-label="Cuenta">
<ul>
<li><a href="/rutas">Mis rutas</a></li>
<li><a href="/ajustes">Ajustes</a></li>
<li><a href="/salir">Cerrar sesión</a></li>
</ul>
</nav>
Con popover="auto" se cierra al pulsar fuera y con Esc. No hace falta aria-expanded, porque el navegador comunica el estado por ti. Un detalle de accesibilidad: no uses role="menu" salvo que programes todo el manejo de flechas del teclado que ese rol promete. Una lista de enlaces normal es más sencilla y más fiable; si quieres profundizar, lee ARIA a fondo.
Un aviso que no molesta
Un mensaje breve de confirmación es un buen candidato a manual con role="status", que hace que los lectores de pantalla lo anuncien sin interrumpir:
<div id="aviso" popover="manual" role="status">Ruta guardada</div>
Muestra el aviso con showPopover() y ciérralo con un temporizador o con un botón. Como manual no cierra los demás popovers ni se cierra al pulsar fuera, la persona puede seguir trabajando mientras lo ve. Recuerda que un aviso que desaparece solo a los pocos segundos puede no dar tiempo a leerlo: si es importante (un error, por ejemplo), déjalo hasta que se cierre con un botón.
¿popover o <dialog>?
Los dos usan la capa superior y los dos tienen ::backdrop, pero sirven para cosas distintas:
popover | <dialog> con showModal() | |
|---|---|---|
| ¿Bloquea el resto de la página? | No | Sí (queda inerte) |
| Cierre con Esc y clic fuera | Sí (auto) | Esc sí; clic fuera, no |
| Ideal para | Menús, globos, avisos, selectores | Confirmaciones y formularios obligatorios |
| ¿Hace falta JavaScript? | No | Sí, para abrirlo |
Pregúntate: ¿la persona tiene que responder antes de continuar? Entonces, <dialog> modal. ¿Es contenido de apoyo que puede ignorar? Entonces, popover.
Compatibilidad
El atributo popover funciona en los navegadores actuales desde 2024. Si necesitas dar soporte a uno antiguo, el contenido de un [popover] que no se entiende se mostraría siempre en la página. Una red de seguridad sencilla es pensar el contenido para que tenga sentido aunque se vea, o comprobar con JavaScript si existe soporte:
if (!HTMLElement.prototype.hasOwnProperty('popover')) {
console.log('Este navegador no admite popover');
}
Errores frecuentes
- El
idno coincide. Sipopovertarget="menu"y el elemento tieneid="menú", el botón no hace nada. Revisa mayúsculas y tildes. - Olvidar
type="button". Dentro de un<form>, un botón sin tipo envía el formulario en vez de abrir el popover. - Pisar el
display. Escribir[popover] { display: flex; }lo deja siempre visible. Aplícalo a[popover]:popover-open. - Usar
popover="manual"y olvidar cerrarlo. No se cierra con Esc ni al pulsar fuera: ofrece un botón. - Usar
popoverpara algo obligatorio. Si hay que decidir antes de seguir, usa un<dialog>modal. - Poner
role="menu"sin teclado. Promete un manejo de flechas que no existe y empeora la accesibilidad. - Llamar a
showPopover()en un elemento sin atributopopover. Lanza un error: añade el atributo.
Resumen
| Elemento o propiedad | Para qué sirve |
|---|---|
popover / popover="auto" | Capa flotante con cierre ligero (clic fuera, Esc) |
popover="manual" | Capa que solo se cierra con botón o JavaScript |
popovertarget="id" | Botón que controla el popover |
popovertargetaction | toggle, show o hide |
:popover-open | Estilo del popover abierto |
::backdrop | Capa detrás del popover |
Evento toggle | Se dispara al abrir o cerrar (newState) |
showPopover(), hidePopover(), togglePopover() | Control desde JavaScript |
<dialog> modal | Cuando la persona debe responder |
En la próxima lección verás cómo reutilizar trozos de HTML con template y componentes. Y para colocar un popover justo al lado de su botón sin calcular coordenadas, tienes Posicionar con anchor y, como base, Posicionamiento.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.