Cómo descartar un diálogo de forma sencilla

Funciones web: <dialog closedby>

Los diálogos modales modernos suelen admitir la opción de "descartar con un toque", lo que permite que los usuarios cierren un diálogo haciendo clic o presionando el fondo (el área fuera del diálogo). El atributo closedby proporciona una forma declarativa de habilitar este comportamiento sin JavaScript personalizado.

Implementación

Para habilitar el cierre con un clic, haz lo siguiente:

  1. Agrega closedby="any" al elemento <dialog>.
  2. Abre el diálogo con dialog.showModal().

Valores del atributo

  • any: Habilita el cierre rápido (hacer clic en el fondo), las "solicitudes de cierre" (la tecla Esc) y los mecanismos para desarrolladores (p.ej., dialog.close()).
  • closerequest: Habilita solo las "solicitudes de cierre" y los mecanismos para desarrolladores. Este es el comportamiento predeterminado de los diálogos modales.
  • none: Solo los mecanismos del desarrollador pueden cerrar el diálogo.

Aplica diseño al fondo

Cuando se abre un diálogo como modal con showModal(), el navegador genera un pseudoelemento ::backdrop. Este fondo cubre todo el viewport y se ubica directamente detrás del diálogo.

/* Style the backdrop to indicate the dialog is modal */
dialog::backdrop {
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px); /* Optional: add blur for modern browsers */
}

Ejemplo

<!-- MANDATORY: Use closedby="any" to enable light-dismiss behavior -->
<dialog id="myDialog" closedby="any" aria-labelledby="dialogTitle">
  <form method="dialog">
    <h2 id="dialogTitle">Feedback</h2>
    <p>Click outside this box or press Esc to dismiss.</p>
    <button type="submit">Close</button>
  </form>
</dialog>

<button onclick="document.getElementById('myDialog').showModal()">Open Dialog</button>

Restricciones y accesibilidad

  • OBLIGATORIO: Usa closedby="any" para habilitar el cierre rápido de forma declarativa.
  • OBLIGATORIO: Abre siempre los diálogos modales con showModal(). Esto garantiza que el diálogo esté en la capa superior, que el enfoque quede atrapado y que se controle la tecla Esc.
  • SÍ: Usa aria-labelledby o aria-label para proporcionar un nombre de accesibilidad para el diálogo.
  • NO uses closedby para diálogos no modales (abiertos con show()), ya que no tienen un fondo y no activarán el cierre con un toque fuera del diálogo.
  • NO uses el evento click para la lógica crítica que debería ocurrir antes del cierre. En su lugar, escucha los eventos close o cancel.

Estrategias de resguardo

OBLIGATORIO: En el caso de los navegadores que aún no admiten closedby, debes implementar un resguardo para el cierre por pérdida de foco. Para ello, verifica si se produjo un clic fuera de los límites del contenido del diálogo con la siguiente secuencia de comandos:

const dialog = document.querySelector('dialog');

// Fallback for browsers without closedby support
if (!('closedBy' in HTMLDialogElement.prototype)) {
  dialog.addEventListener('click', (event) => {
    // 1. When clicking the backdrop, the event target is the dialog element itself.
    // Ignore clicks where the target is a child element inside the dialog.
    if (event.target !== dialog) return;

    // 2. Check if the click coordinates fall within the dialog's content box.
    // This distinguishes between a click on the backdrop vs a click on the dialog's background/padding.
    const rect = dialog.getBoundingClientRect();
    const isDialogContent = (
      rect.top <= event.clientY &&
      event.clientY <= rect.top + rect.height &&
      rect.left <= event.clientX &&
      event.clientX <= rect.left + rect.width
    );

    if (isDialogContent) return;

    // 3. Since the click was outside the content area (on the backdrop), manually close the dialog.
    dialog.close();
  });
}