0Apoyar

Playground de dialog en HTML

Explora dialog con una previsualización mínima formada solo por el botón de apertura y el modal. Cambia command, commandfor y closedby sin generar CSS.

Leer la guía

Preguntar a la IA

Playground de dialog en HTML

Respuesta generada con openai/gpt-5.4-nano. La IA puede cometer errores. Revisa siempre el resultado.
HTML
<button type="button" commandfor="baseline-dialog" command="show-modal">
  Abrir modal
</button>

<dialog id="baseline-dialog" closedby="any">
  <h2>Dialog nativo</h2>
  <p>Este modal usa dialog, command, commandfor y closedby sin añadir CSS.</p>
  <button type="button" commandfor="baseline-dialog" command="close">
    Cerrar
  </button>
</dialog>
JS

Guía educativa de HTML dialog

Pedir atención con un diálogo nativo

Una confirmación necesita una decisión; un inspector puede convivir con la página. Aprende a elegir entre modal y no modal, abrir y cerrar dialog, dirigir el foco y recuperar una respuesta sin construir tu propia capa de interacción.

Volver al playground

Modelo mental

dialog aporta la semántica de una ventana de interacción. El método de apertura decide si bloquea el resto del documento.

Una ventana, dos formas de participarEsquema estático conceptual. Las etiquetas explican qué parte permanece operable; las recetas siguientes prueban el comportamiento nativo.1 · El modal está en la top layer; la interacción queda dentro.2 · El no modal convive con la página; el foco puede salir.

showModal() sitúa el diálogo en la top layer y vuelve inerte el resto de su documento: no se puede pulsar ni enfocar el contenido exterior mientras siga abierto. El navegador gestiona esa modalidad; no hace falta recrearla con un z-index enorme ni un bucle de Tab.

show() abre una ventana no modal: recibe foco inicial, pero la página sigue disponible y Tab puede salir del diálogo. No se añade a la top layer ni genera el backdrop de un modal. Su posición se puede adaptar con CSS, como el panel en flujo de esta guía.

El atributo open indica que dialog está abierto, pero no dice que sea modal. Escribir <dialog open> lo muestra como no modal y no ejecuta los pasos de foco de show(). Para abrir usa la API o los comandos; para cerrar usa close() o un formulario de diálogo.

Abrir según la tarea

Una confirmación breve puede interrumpir el flujo. Un panel de consulta suele necesitar que el usuario continúe trabajando fuera.

Con command="show-modal" y commandfor, el botón apunta al id de dialog y solicita apertura modal sin un listener de apertura. La receta añade JavaScript solo para el fallback en navegadores sin comandos y para mostrar returnValue. No dispara showModal() una segunda vez cuando hay soporte nativo.

Confirmación modal con una respuesta

¿Aplicar estas preferencias?

Esta demostración pide una decisión, pero no guarda ningún dato. Puedes confirmar o volver a la página.

Última respuesta: Sin respuesta

Abre el diálogo y prueba Tab, Mayús+Tab y Escape. Cancelar y Confirmar cierran con method="dialog"; el resultado muestra cancel o confirm. Escape cierra sin una respuesta nueva. El resto de la página queda inerte durante la apertura.
Confirmación modal con una respuesta
<button id="dialog-guide-modal-opener" type="button" class="dialog-guide-modal-control" commandfor="dialog-guide-modal" command="show-modal">
  Revisar preferencias
</button>
<dialog id="dialog-guide-modal" aria-labelledby="dialog-guide-modal-heading" aria-describedby="dialog-guide-modal-body">
  <form method="dialog">
    <h5 id="dialog-guide-modal-heading">¿Aplicar estas preferencias?</h5>
    <p id="dialog-guide-modal-body">Esta demostración pide una decisión, pero no guarda ningún dato. Puedes confirmar o volver a la página.</p>
    <div class="dialog-guide-modal-actions">
      <button type="submit" class="dialog-guide-modal-control" value="cancel" formnovalidate autofocus>
        Cancelar
      </button>
      <button type="submit" class="dialog-guide-modal-control" value="confirm">
        Confirmar
      </button>
    </div>
  </form>
</dialog>
<p class="dialog-guide-modal-result">
  Última respuesta:
  <output id="dialog-guide-modal-result" aria-live="polite" data-empty="Sin respuesta">Sin respuesta</output>
</p>
:root {
  color-scheme: light dark;
}

.dialog-guide-modal-control {
  min-block-size: 2.75rem;
  max-inline-size: 100%;
  padding: 0.5rem 1rem;
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 98%, white), color-mix(in oklch, oklch(0.22 0.024 260) 86%, black));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font: inherit;
  overflow-wrap: anywhere;
  cursor: pointer;

  &:focus-visible {
    outline: 3px dashed light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
    outline-offset: 3px;
  }

  &:disabled {
    color: light-dark(oklch(0.38 0.008 95), oklch(0.78 0.018 260));
    cursor: default;
  }
}

#dialog-guide-modal {
  box-sizing: border-box;
  inline-size: min(28rem, calc(100vi - 2rem));
  max-inline-size: 100%;
  max-block-size: 75dvb;
  overflow: auto;
  padding: min(1rem, 4vi);
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(oklch(0.98 0.005 95), oklch(0.16 0.02 260));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font-size: 1rem;
  line-height: 1.65;

  form {
    display: grid;
    gap: 1rem;
  }

  h5 {
    margin: 0;
    font-size: 1.25rem;
  }

  p {
    margin: 0;
  }

  &::backdrop {
    background: oklch(0.16 0.02 260 / 0.55);
  }
}

.dialog-guide-modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.dialog-guide-modal-result {
  margin-block-start: 1rem;
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font-size: 1rem;
  overflow-wrap: anywhere;
}
(() => {
  const dialog = document.getElementById('dialog-guide-modal');
  const opener = document.getElementById('dialog-guide-modal-opener');
  const result = document.getElementById('dialog-guide-modal-result');

  if (!dialog || !opener || dialog.dataset.bound) return;
  if (typeof dialog.showModal !== 'function') {
    opener.disabled = true;
    return;
  }
  dialog.dataset.bound = 'true';
  const nativeCommands = 'commandForElement' in HTMLButtonElement.prototype;

  opener.addEventListener('click', () => {
    if (dialog.open) return;
    dialog.returnValue = '';
    if (!nativeCommands) dialog.showModal();
  });

  dialog.addEventListener('close', () => {
    if (result) result.textContent = dialog.returnValue || result.dataset.empty;
  });
})();

Panel de consulta sin bloquear la página

Notas de edición

Este panel sigue disponible mientras recorres la guía. No necesitas cerrarlo para continuar con otra parte de la página.

El botón llama a show() y abre el panel dentro del flujo. Tab puede seguir hacia el resto de la guía; Escape no lo cierra por defecto. Usa Cerrar panel para salir de forma explícita.
Panel de consulta sin bloquear la página
<button id="dialog-guide-nonmodal-opener" type="button" class="dialog-guide-nonmodal-control" disabled>
  Abrir panel de consulta
</button>
<dialog id="dialog-guide-nonmodal" aria-labelledby="dialog-guide-nonmodal-heading" aria-describedby="dialog-guide-nonmodal-body">
  <form method="dialog">
    <h5 id="dialog-guide-nonmodal-heading">Notas de edición</h5>
    <p id="dialog-guide-nonmodal-body">Este panel sigue disponible mientras recorres la guía. No necesitas cerrarlo para continuar con otra parte de la página.</p>
    <div class="dialog-guide-nonmodal-actions">
      <button type="submit" class="dialog-guide-nonmodal-control" value="close" formnovalidate autofocus>
        Cerrar panel
      </button>
    </div>
  </form>
</dialog>
:root {
  color-scheme: light dark;
}

.dialog-guide-nonmodal-control {
  min-block-size: 2.75rem;
  max-inline-size: 100%;
  padding: 0.5rem 1rem;
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 98%, white), color-mix(in oklch, oklch(0.22 0.024 260) 86%, black));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font: inherit;
  overflow-wrap: anywhere;
  cursor: pointer;

  &:focus-visible {
    outline: 3px dashed light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
    outline-offset: 3px;
  }

  &:disabled {
    color: light-dark(oklch(0.38 0.008 95), oklch(0.78 0.018 260));
    cursor: default;
  }
}

#dialog-guide-nonmodal {
  box-sizing: border-box;
  inline-size: min(28rem, 100%);
  max-inline-size: 100%;
  max-block-size: 75dvb;
  overflow: auto;
  padding: min(1rem, 4vi);
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(oklch(0.98 0.005 95), oklch(0.16 0.02 260));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font-size: 1rem;
  line-height: 1.65;
  position: static;
  margin: 1rem 0 0;

  form {
    display: grid;
    gap: 1rem;
  }

  h5 {
    margin: 0;
    font-size: 1.25rem;
  }

  p {
    margin: 0;
  }
}

.dialog-guide-nonmodal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
(() => {
  const dialog = document.getElementById('dialog-guide-nonmodal');
  const opener = document.getElementById('dialog-guide-nonmodal-opener');

  if (!dialog || !opener || dialog.dataset.bound) return;
  if (typeof dialog.show !== 'function') {
    opener.disabled = true;
    return;
  }
  dialog.dataset.bound = 'true';
  opener.disabled = false;

  opener.addEventListener('click', () => {
    if (dialog.open) return;
    dialog.returnValue = '';
    dialog.show();
  });
})();

El HTML, CSS y JavaScript de cada receta forman un ejemplo completo. Los IDs son propios de la guía y no cambian el playground. Sin JS, la confirmación funciona si el navegador admite los comandos, aunque el resultado no se actualiza; la apertura no modal permanece deshabilitada.

No conviertas un diálogo ya abierto con show() en modal llamando a showModal(): puede lanzar InvalidStateError. Ciérralo primero y vuelve a abrirlo en el modo elegido. El guard dialog.open de las recetas evita repetir la apertura del mismo ejemplo.

Foco y elección del patrón

La modalidad define el recorrido, pero el contenido define dónde conviene empezar. Un nombre accesible y una salida visible siguen siendo necesarios.

aria-labelledby conecta el diálogo con su título visible. aria-describedby sirve aquí porque la explicación es breve; no lo apliques a todo un contenido largo con listas o tablas que necesita explorarse por estructura. dialog ya aporta su rol; showModal() aporta la modalidad accesible.

Usa autofocus en el control que tenga sentido al entrar. Esta confirmación empieza en Cancelar, la opción segura; un formulario puede empezar en su primer campo. Para texto largo, puede convenir un encabezado interior con tabindex="-1" como destino de foco. No añadas tabindex al elemento dialog ni fuerces el foco con cada render.

Al cerrar, el navegador puede devolver el foco al elemento que estaba enfocado antes de abrir. En el no modal no debe robarlo si el usuario ya se fue fuera. Comprueba el recorrido real; si el disparador desaparece tras la acción, dirige el foco a un destino lógico de la nueva pantalla.

dialog: tarea en una ventana

Elige modalidad para una decisión que necesita completarse o cancelarse antes de continuar. Usa show() para una ventana de interacción que puede convivir con la página. Reserva la interrupción para una necesidad concreta.

Popover: contenido complementario

Ayuda contextual, acciones o contenido temporal que no debe bloquear el documento. Popover ofrece top layer y modos de cierre, pero sigue siendo no modal: estar por encima no equivale a volver inerte lo demás.

details: contenido desplegable

Una explicación que se expande en el flujo mediante summary, sin una ventana de diálogo ni modalidad. Encaja cuando el contenido pertenece a esa sección y solo necesita mostrarse u ocultarse.

Cerrar y solicitar cierre

Ofrece siempre un control explícito. Escape, el toque exterior y una respuesta de formulario son caminos distintos, no una única señal de confirmación.

Por defecto, un modal acepta solicitudes de cierre como Escape y no se cierra al pulsar el exterior. El no modal no acepta Escape por defecto. No bases la salida únicamente en el teclado: conserva Cancelar o Cerrar para ratón, tacto y tecnologías de asistencia.

closedby="any"

Añade closedby="any" si quieres cierre exterior, solicitudes de la plataforma y mecanismos explícitos. Trátalo como mejora progresiva: sin soporte de closedby, el exterior no cerrará el modal, pero el botón y Escape siguen siendo salidas.

closedby="closerequest"

closedby="closerequest" acepta solicitudes de la plataforma y mecanismos explícitos, sin cierre exterior. Es el comportamiento modal por defecto cuando el atributo falta o no tiene un valor válido; no promete el mismo gesto en todas las plataformas.

closedby="none"

closedby="none" permite mecanismos explícitos y desactiva el cierre exterior y las solicitudes del usuario donde se admite. No lo uses para obligar a confirmar. Un navegador que ignore el atributo conserva su comportamiento por defecto.

cancel se dispara ante una solicitud de cierre, por ejemplo Escape. Se escucha directamente en dialog y puede impedirse con preventDefault() cuando sea necesario proteger trabajo pendiente; debe existir una salida clara. No se dispara simplemente por llamar a close() ni por enviar method="dialog".

close avisa de que el diálogo ya se ha cerrado; no es cancelable y no burbujea. Úsalo para recoger el resultado o limpiar recursos. close(valor) cierra directamente; requestClose(valor) solicita cierre y pasa por cancel, por lo que puede rechazarse. Detecta requestClose por separado antes de usarlo.

Formularios y resultado

Cerrar una ventana y guardar datos son operaciones distintas. La confirmación anterior solo comunica una elección local.

method="dialog" cierra el diálogo al enviar el formulario y pone el value del botón de envío en returnValue. No hace una petición de red ni borra los campos. formmethod="dialog" permite reservar este comportamiento para un botón de un formulario que use otro método.

La validación nativa sigue aplicándose al envío: un campo required vacío puede impedir el cierre. Por eso la acción Cancelar lleva formnovalidate; descartar no debe exigir completar un formulario. Un botón que solo abre o llama a close() debe tener type="button" para no enviar por accidente.

Lee returnValue en close, pero no asumas que cualquier cierre confirma. Escape o close() sin argumento pueden conservar un valor anterior. La receta lo reinicia antes de abrir. Para una acción real, procesa solo confirm como confirmación explícita; cancel y la cadena vacía son salidas sin confirmar.

Si necesitas guardar datos, envíalos y gestiona validación, espera y errores antes de cerrar. method="dialog" no sustituye a ese proceso. La guía no ejecuta una operación real: el resultado permite observar la diferencia entre elección y descarte.

Estilos sin perder comportamiento

El aspecto puede adaptarse al sitio sin reemplazar el funcionamiento nativo. Conserva espacio para texto, acciones y foco.

El navegador oculta dialog cuando no tiene open. Evita una regla incondicional dialog { display: grid; }, porque podría mostrarlo cerrado. Si necesitas cambiar su display, limita la regla a dialog[open]. El form interior de las recetas puede usar Grid sin tocar esa visibilidad.

dialog::backdrop pinta la capa situada detrás del modal en la top layer. No es un nodo del DOM, no añade cierre exterior y no aparece al abrir con show(). Cambiar su color solo ayuda a reconocer el contexto bloqueado.

Limita el ancho al viewport y permite scroll interior para ventanas bajas y texto ampliado. El panel y los botones deben envolver y conservar el foco visible. La receta usa propiedades lógicas y un límite de altura; no añade animaciones, de modo que la interacción también funciona con movimiento reducido.

Errores frecuentes

Reconoce el síntoma y corrige la causa antes de añadir otro overlay.

Se ve, pero la página sigue operable

Lo que esperabas
Mostrar un modal que bloquee el documento.
Por qué ocurre
open o show() abren un diálogo no modal; z-index y aria-modal no cambian eso.
Cómo corregirlo
Abre con showModal() o command="show-modal" y comprueba foco y Tab.

El botón declarativo no abre

Lo que esperabas
Abrir el diálogo desde HTML en cualquier navegador.
Por qué ocurre
Falta soporte de comandos o commandfor no coincide con un id único.
Cómo corregirlo
Revisa la asociación y detecta commandForElement. Usa showModal() como fallback local, sin ejecutar ambos caminos a la vez.

Cancelar exige completar campos

Lo que esperabas
Descartar una edición aunque todavía haya campos inválidos.
Por qué ocurre
Un botón submit dentro de method="dialog" también activa validación.
Cómo corregirlo
Añade formnovalidate a Cancelar o usa un botón type="button" que llame a close().

Escape parece repetir una confirmación

Lo que esperabas
Distinguir una confirmación nueva de un cierre sin elección.
Por qué ocurre
returnValue puede conservar el valor de la apertura anterior.
Cómo corregirlo
Reinícialo antes de abrir y procesa solo el valor explícito de la acción de confirmación.

El diálogo aparece incluso cerrado

Lo que esperabas
Ocultarlo al finalizar la tarea.
Por qué ocurre
Una regla display en dialog sobrescribe el estilo de ocultación del navegador.
Cómo corregirlo
Aplica display solo con [open] y cierra mediante la API, no quitando open a mano.

Soporte y mejora progresiva

El elemento, los comandos y las opciones de cierre tienen estados diferentes. Que el modal funcione no verifica todas sus extensiones.

dialog, show(), showModal() y method="dialog" forman la base Widely Available. Esta base permite una ventana nativa sin recrear modalidad o semántica. El lab separa HTML declarativo de JavaScript para explorar ambos caminos.

command y commandfor son Newly Available, Baseline desde el 12 de diciembre de 2025. La receta detecta commandForElement en HTMLButtonElement.prototype y usa showModal() cuando falta: el cierre por formulario sigue siendo nativo.

closedby tiene disponibilidad limitada: las fuentes consultadas registran soporte en Chromium y Firefox, pero no en Safari. La guía explica sus valores como mejora opcional y conserva el cierre explícito, sin prometer light dismiss donde no existe.

Comprueba typeof dialog.showModal === "function" para la apertura modal y "closedBy" in HTMLDialogElement.prototype para esa extensión. La presencia de un atributo en el HTML no demuestra que funcione; tampoco la tarjeta Baseline certifica este navegador ni una prueba de accesibilidad.

Para una tarea esencial sin soporte de dialog, ofrece el formulario o la confirmación en una página o sección convencional. Mostrar un bloque con CSS no reproduce top layer, inercia o restauración de foco. El contenido editorial y los bloques de código de esta guía se sirven completos sin JavaScript.