Saltar al contenido
popover.html · devschool

Popover en HTML: menús, avisos y globos sin JavaScript

Lección 22 de 39 · 10 min de lectura · Actualizado el

En esta lección
  1. Lo mínimo: un botón y una capa
  2. Los tipos de popover: auto y manual
  3. Qué hace cada botón: popovertargetaction
  4. Estilo: el popover es un elemento más
  5. El evento toggle
  6. Controlarlo desde JavaScript
  7. Dos usos típicos
  8. ¿popover o <dialog>?
  9. Compatibilidad
  10. Errores frecuentes
  11. Resumen

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:

ValorSe cierra al…Otros popovers abiertos
popover o popover="auto"Pulsar fuera, pulsar Esc o pulsar otra vez el botónAbrirlo cierra los demás (salvo sus padres)
popover="manual"Pulsar su botón de cerrar o con JavaScriptNo 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 popovers auto que ya estén abiertos. Es nuevo y no todos los navegadores lo admiten, así que de momento quédate con auto y manual.

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étodoQué 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.popover devuelve "auto", "manual" o null. Un elemento sin el atributo no puede abrirse con showPopover(): 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?NoSí (queda inerte)
Cierre con Esc y clic fueraSí (auto)Esc sí; clic fuera, no
Ideal paraMenús, globos, avisos, selectoresConfirmaciones y formularios obligatorios
¿Hace falta JavaScript?NoSí, 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 id no coincide. Si popovertarget="menu" y el elemento tiene id="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 popover para 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 atributo popover. Lanza un error: añade el atributo.

Resumen

Elemento o propiedadPara 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
popovertargetactiontoggle, show o hide
:popover-openEstilo del popover abierto
::backdropCapa detrás del popover
Evento toggleSe dispara al abrir o cerrar (newState)
showPopover(), hidePopover(), togglePopover()Control desde JavaScript
<dialog> modalCuando 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).

popover.html
Resultado

Pon a prueba lo que has aprendido

[HTML] ¿Qué valor de popover hace que la capa solo se cierre con su botón o desde JavaScript?

[HTML] ¿Qué ocurre al pulsar este botón cuando el popover ya está abierto?
<button type="button" popovertarget="info">Info</button>
<div id="info" popover>Texto</div>

[HTML] ¿Qué dice el evento toggle de un popover cuando se acaba de abrir?

[HTML] Un aviso de "Guardado" debe dejar seguir trabajando y cerrarse a mano o con un temporizador. ¿Cuál es la mejor opción?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.