Modales y popovers nativos: <dialog> y la Popover API

Aprende a crear modales, confirmaciones y paneles flotantes con el elemento nativo dialog y la Popover API de HTML. Sin librerías, sin JavaScript extra.

Durante años, crear un modal requería librerías externas, gestionar z-index, bloquear el scroll del <body>, capturar el foco y muchas otras cosas tediosas. El HTML moderno resuelve todo esto de forma nativa con <dialog> y la Popover API.


El elemento <dialog>

<dialog> es un elemento HTML diseñado específicamente para ventanas de diálogo, modales y confirmaciones. El navegador gestiona por ti el foco, el bloqueo del scroll y la capa visual (backdrop).

Uso básico

<dialog id="mi-modal">
  <h2>¿Estás seguro?</h2>
  <p>Esta acción no se puede deshacer.</p>
  <button id="btn-cerrar">Cancelar</button>
  <button>Confirmar</button>
</dialog>

<button id="btn-abrir">Abrir modal</button>

<script>
  const modal = document.getElementById('mi-modal');
  document.getElementById('btn-abrir').addEventListener('click', () => {
    modal.showModal(); // abre como modal (bloquea el fondo)
  });
  document.getElementById('btn-cerrar').addEventListener('click', () => {
    modal.close();
  });
</script>

show() vs showModal()

MétodoComportamiento
show()Abre el diálogo como elemento no modal (sin bloquear fondo)
showModal()Abre como modal real: bloquea interacción con el resto, gestiona foco, añade backdrop

En casi todos los casos querrás showModal().

Cerrar con Escape

El navegador añade automáticamente la funcionalidad de cerrar el modal con la tecla Escape cuando se usa showModal(). No tienes que programarlo.

Cerrar al hacer clic fuera del modal

Esto sí requiere un poco de JavaScript:

<dialog id="modal">
  <div class="contenido-modal">
    <p>Contenido del modal.</p>
    <button id="cerrar">Cerrar</button>
  </div>
</dialog>

<script>
  const modal = document.getElementById('modal');

  // El click en el backdrop (la capa oscura) llega al <dialog>
  modal.addEventListener('click', (e) => {
    if (e.target === modal) modal.close();
  });
</script>

Estilizar el backdrop

El navegador añade automáticamente un fondo semitransparente. Puedes personalizarlo con CSS:

dialog::backdrop {
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
}

dialog {
  border: none;
  border-radius: 12px;
  padding: 2rem;
  max-width: 480px;
  width: 90%;
}

Formularios dentro de <dialog>

<dialog> tiene integración nativa con formularios. Un <form method="dialog"> cierra el modal al enviarse y expone el valor del botón pulsado mediante dialog.returnValue:

<dialog id="confirmar">
  <p>¿Quieres guardar los cambios?</p>
  <form method="dialog">
    <button value="cancelar">Cancelar</button>
    <button value="guardar">Guardar</button>
  </form>
</dialog>

<script>
  const dialog = document.getElementById('confirmar');
  dialog.addEventListener('close', () => {
    console.log('Botón pulsado:', dialog.returnValue);
    // "cancelar" o "guardar"
  });
</script>

La Popover API

La Popover API es más reciente que <dialog> y está pensada para elementos que aparecen sobre el contenido sin bloquear la interacción: tooltips, menús desplegables, notificaciones, paneles de información.

La diferencia clave con <dialog>

  • <dialog> → modal que bloquea el fondo. El usuario debe atenderlo.
  • popover → elemento flotante no bloqueante. El usuario puede ignorarlo.

Uso básico

Solo con HTML, sin JavaScript:

<!-- El botón activa el popover con popovertarget -->
<button popovertarget="mi-panel">Mostrar panel</button>

<!-- El elemento flotante lleva el atributo popover -->
<div id="mi-panel" popover>
  <p>Soy un popover nativo. Ciérrame pulsando fuera.</p>
</div>

El navegador gestiona automáticamente:

  • Abrir y cerrar al hacer clic fuera.
  • Cerrar al pulsar Escape.
  • La capa de apilamiento (top layer), por encima de cualquier z-index.

Tipos de popover

<!-- auto (por defecto): se cierra al hacer clic fuera o al abrir otro popover -->
<div id="panel-auto" popover="auto">...</div>

<!-- manual: solo se cierra mediante JavaScript o el botón de cierre -->
<div id="panel-manual" popover="manual">...</div>

Control con JavaScript

const panel = document.getElementById('mi-panel');

panel.showPopover();  // mostrar
panel.hidePopover();  // ocultar
panel.togglePopover(); // alternar

Tooltip con Popover API

<button popovertarget="tip-1" popovertargetaction="toggle">
  ¿Qué es el LCP?
</button>

<div id="tip-1" popover role="tooltip">
  <strong>Largest Contentful Paint</strong>: tiempo que tarda en renderizarse 
  el elemento visual más grande de la página.
</div>

Estilizar el popover

[popover] {
  border: 1px solid #333;
  border-radius: 8px;
  padding: 1rem;
  background: #1a1a2e;
  color: #fff;
  max-width: 300px;
}

/* Estado abierto (nuevo en CSS) */
[popover]:popover-open {
  animation: fadeIn 0.2s ease;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

<dialog> vs Popover: cuándo usar cada uno

Caso de usoUsar
Confirmación antes de eliminar algo<dialog>
Formulario en ventana emergente<dialog>
Tooltip de ayudaPopover API
Menú desplegablePopover API
Notificación / toastPopover API
Búsqueda global (overlay)<dialog>
Panel lateral (sin bloquear fondo)Popover API

¿Qué hemos aprendido?

  • <dialog> con showModal() gestiona el backdrop, el foco y el Escape de forma nativa.
  • <form method="dialog"> permite capturar qué botón cerró el modal con dialog.returnValue.
  • La Popover API crea elementos flotantes no bloqueantes solo con atributos HTML.
  • Ambos se colocan en el top layer, por encima de cualquier z-index.

Siguiente paso

En la próxima lección veremos <details> y <summary>: cómo crear acordeones, secciones colapsables y FAQs sin JavaScript.