Etiqueta dialog en HTML: ventanas modales, cierre y foco
La etiqueta <dialog> representa un cuadro de diálogo que puede mostrarse de forma modal o no modal. Sirve para concentrar una tarea temporal, como confirmar si quieres archivar un borrador, y volver después al trabajo anterior. Úsala cuando esa decisión necesita su propio cuadro; para información adicional que se despliega dentro de la página suele bastar <details>.
Ejemplo mínimo: un diálogo que empieza oculto
<dialog id="confirmacion" aria-labelledby="titulo-dialogo">
<h2 id="titulo-dialogo">Confirmar acción</h2>
<form method="dialog">
<button type="submit">Cerrar</button>
</form>
</dialog>
Este fragmento no muestra nada al cargar: falta una acción de apertura. El formulario permite cerrar el cuadro una vez abierto, pero no lo abre. Puedes pegarlo dentro del <body> de un documento HTML y comprobar que empieza oculto; el caso práctico de esta guía añade el botón invocador y toda la lógica necesaria.
Sintaxis y contexto válido
Escribe <dialog>…</dialog> con ambas etiquetas: no se pueden omitir. Es contenido de flujo y admite contenido de flujo, como encabezados, párrafos y formularios. Puede ir dentro de una sección o del cuerpo del documento, pero no dentro de un <p>. Si contiene un formulario, evita introducirlo dentro de otro formulario: HTML no permite anidarlos.
Sin open, el navegador lo oculta normalmente. Con open ya está visible, pero como diálogo no modal. El atributo es booleano: open="false" sigue estando presente y no lo cierra; además, ese valor no es una escritura válida de un booleano. Para el estado inicial cerrado, omítelo.
No asignes tabindex al propio <dialog>: el estándar lo prohíbe. El foco inicial debe elegirse con cuidado, normalmente en un control descendiente mediante autofocus. Estas reglas se contrastaron con el modelo de contenido y los atributos de dialog.
Modal frente a no modal: show y showModal
Las dos funciones muestran un elemento que ya existe en el documento:
| Apertura | Comportamiento |
|---|---|
dialogo.show() | Abre sin bloquear la interacción con el resto de la página. |
dialogo.showModal() | Abre de forma modal: el resto del documento queda inerte mientras permanece abierto. |
Atributo open | Declara un cuadro visible no modal; no ejecuta los pasos de foco de las funciones. |
showModal() coloca el cuadro en la top layer, la capa superior que administra el navegador. Por eso puede aparecer por encima de los contenedores de la página sin una competición de valores z-index. Además, genera ::backdrop, un pseudoelemento que puedes colorear con CSS para separar visualmente el cuadro de su fondo. Consulta el funcionamiento de showModal.
¿Open crea una ventana modal? No. Tampoco lo hacen display: block, un fondo oscuro ni aria-modal="true". Usa showModal() o un mecanismo declarativo compatible. El navegador proporciona la modalidad y la semántica correspondiente; no necesitas añadir role="dialog" al elemento nativo.
Ejemplo práctico: confirmar el archivo de un borrador
Guarda el documento completo como confirmar-borrador.html en UTF-8 y ábrelo en un navegador. No requiere servidor ni archivos externos. Es una demostración local: registra la elección en pantalla, no archiva archivos, no envía datos y no guarda el resultado.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Confirmar el archivo de un borrador</title>
<style>
body { font-family: system-ui; max-width: 42rem; margin: auto; padding: 1rem; }
dialog {
box-sizing: border-box;
width: min(28rem, calc(100% - 2rem));
max-height: calc(100% - 2rem);
overflow: auto;
border: 2px solid #555;
padding: 1rem;
}
dialog::backdrop { background: rgb(0 0 0 / 0.55); }
form { display: flex; flex-wrap: wrap; gap: 0.75rem; }
button { font: inherit; padding: 0.5rem 0.75rem; }
button:focus-visible { outline: 3px solid #164db5; outline-offset: 3px; }
</style>
</head>
<body>
<h1>Revisar un borrador</h1>
<p>El borrador de la agenda está listo para revisión. Archivar lo apartaría
de la lista de trabajo; conserva el original si todavía necesitas editarlo.</p>
<p>Esta práctica solo registra una elección mientras mantienes la página abierta.</p>
<button id="dialog-abrir" type="button" hidden>Revisar archivo del borrador</button>
<p id="dialog-alternativa">Si no puedes abrir el cuadro, revisa la propuesta
anterior y conserva el borrador original. Esta página no modifica archivos.</p>
<p id="dialog-resultado" role="status">Todavía no has elegido una opción.</p>
<dialog id="dialog-borrador" aria-labelledby="dialog-titulo"
aria-describedby="dialog-descripcion" hidden>
<h2 id="dialog-titulo">¿Archivar el borrador?</h2>
<p id="dialog-descripcion">Confirma el archivo de la agenda o cancela
para continuar revisándola.</p>
<form method="dialog">
<button id="dialog-cancelar" type="button" autofocus>Cancelar</button>
<button type="submit" value="confirmar">Confirmar archivo</button>
</form>
</dialog>
<script>
const abrir = document.getElementById("dialog-abrir");
const dialogo = document.getElementById("dialog-borrador");
const cancelar = document.getElementById("dialog-cancelar");
const resultado = document.getElementById("dialog-resultado");
const alternativa = document.getElementById("dialog-alternativa");
if (typeof dialogo.showModal === "function" &&
typeof dialogo.close === "function") {
abrir.addEventListener("click", () => {
dialogo.returnValue = "";
dialogo.showModal();
});
cancelar.addEventListener("click", () => {
dialogo.close("cancelar");
});
dialogo.addEventListener("close", () => {
resultado.textContent = dialogo.returnValue === "confirmar"
? "Has elegido archivar en esta práctica. No se ha guardado ningún cambio."
: "Has cancelado. El borrador se conserva.";
abrir.focus();
});
dialogo.hidden = false;
alternativa.hidden = true;
abrir.hidden = false;
}
</script>
</body>
</html>
El botón solo aparece cuando el script encuentra las APIs necesarias. hidden mantiene ocultos el botón y el cuadro si JavaScript está desactivado o falta soporte; en ese caso queda visible la explicación alternativa. Al habilitar la función, el script retira hidden del cuadro: como todavía no tiene open, sigue cerrado hasta llamar a showModal().
El título da nombre accesible al cuadro mediante aria-labelledby. La descripción breve usa aria-describedby. El foco comienza en «Cancelar» gracias a autofocus, para que abrir la confirmación no favorezca una acción involuntaria. El botón «Confirmar archivo» envía el formulario con method="dialog": el navegador cierra el cuadro y copia su value en returnValue. No hay una petición HTTP.
Comprueba estas acciones:
- Llega al botón invocador con Tab y pulsa Enter. Deben aparecer el cuadro y su fondo; «Cancelar» recibe foco visible.
- Usa Tab y Mayús + Tab. Puedes recorrer los controles del cuadro; el contenido de fondo no debe recibir foco mientras está modal.
- Pulsa Escape. Se cancela, se actualiza el mensaje y el foco vuelve al botón que abrió el cuadro.
- Reabre y activa «Confirmar archivo». El mensaje cambia a la elección confirmada. Reabre otra vez y activa «Cancelar»: vuelve el mensaje de cancelación, sin conservar la confirmación anterior.
- Deshabilita JavaScript y recarga. Permanecen la información del borrador y la alternativa; el botón y el cuadro no aparecen. Para provocar el mismo camino con JavaScript activo, cambia temporalmente la condición del
ifafalse. - Reduce la ventana a tamaño móvil y aumenta el texto. Comprueba que el título y los controles siguen legibles y alcanzables mediante desplazamiento.
Si cambias showModal() por show(), el cuadro será no modal: podrás interactuar con la página de fondo y Escape no lo cerrará por defecto. Quitar el CSS conserva la interacción nativa, con otra apariencia. Sustituir <dialog> por <div> deja sin showModal(), close() y cierre mediante method="dialog"; recrear una modalidad exige implementar más comportamiento y accesibilidad.
Cerrar correctamente: cancel, close y returnValue
¿Cómo cierro un dialog correctamente? Elige el mecanismo según la intención:
close("cancelar")cierra directamente y establece el valor indicado. No emitecancel.requestClose("cancelar")solicita el cierre y emitecancel; un listener puede impedirlo conevent.preventDefault(). Si se permite, cierra y emiteclose.- Un formulario descendiente con
method="dialog"cierra al enviarse y devuelve el valor del botón de envío. No emitecancel.
Escape provoca una solicitud de cierre en el modal por defecto y emite cancel. El ejemplo deja continuar ese cierre y trata cualquier resultado distinto de "confirmar" como una cancelación; Escape no equivale a enviar un botón del formulario. close reúne la actualización de pantalla y el regreso de foco. El evento close no se puede cancelar. Consulta cancel, close y requestClose.
returnValue es una propiedad DOM de tipo cadena, inicialmente vacía. Abrir no la reinicia automáticamente: el ejemplo la vacía en cada apertura para evitar interpretar una decisión antigua como nueva. El navegador restaura el foco previo en el cierre modal cuando corresponde; aquí también se devuelve explícitamente al invocador, que permanece disponible. Si lo eliminas, elige un destino lógico para continuar, siguiendo las recomendaciones de foco de WAI.
Atributos específicos y funciones nuevas
open y closedby son atributos de <dialog>. En navegadores compatibles, closedby establece qué acciones de usuario permiten cerrar:
| Valor de closedby | Cierre permitido |
|---|---|
any | Clic fuera del cuadro, solicitud de cierre como Escape y mecanismos de la aplicación. |
closerequest | Solicitudes como Escape y mecanismos de la aplicación; no clic fuera. |
none | Mecanismos de la aplicación, como un botón que llama a close(). |
Si falta o tiene un valor inválido, se comporta como closerequest para el modal y como none para el no modal. No es booleano. La referencia de dialog en MDN explica estos valores. Mantén siempre un cierre visible: el ejemplo principal no depende de closedby ni del clic fuera.
id, hidden y autofocus son globales; los atributos ARIA aportan nombre y descripción accesibles. show(), showModal(), close() y requestClose() son métodos JavaScript; returnValue es una propiedad. method="dialog" pertenece a <form>, y ::backdrop pertenece a CSS.
También puedes abrir el diálogo mínimo, sin script, con este botón si el navegador admite ambas funciones declarativas:
<button type="button" command="show-modal" commandfor="confirmacion">
Abrir confirmación
</button>
Colócalo junto al primer fragmento. commandfor apunta al id; command indica la acción. Pertenecen al botón, no al diálogo. Existen también close y request-close, con diferencias equivalentes a las APIs; el segundo comando tiene su propia compatibilidad. Consulta los comandos de button. Para navegadores anteriores, usa la vía con JavaScript del ejemplo práctico y conserva su alternativa.
Errores frecuentes y etiquetas relacionadas
Si no aparece el cuadro, comprueba que la selección del elemento devuelve el diálogo correcto, que el script se ejecuta después del HTML y que no conserva hidden. Si showModal() falla porque ya estaba abierto como no modal, ciérralo antes de cambiar de modo.
No retires open manualmente para cerrar: puedes omitir eventos y dejar el documento bloqueado. No fuerces display: block sobre un diálogo cerrado. No ocultes el cierre ni bloquees Escape sin una necesidad concreta y una salida accesible. Evita cuadros demasiado largos y prueba el foco con teclado; el anuncio de un lector de pantalla depende del navegador y de la tecnología de asistencia.
<button> invoca la acción y permite cancelarla; <form> organiza la confirmación; <script> aporta aquí los manejadores. <details> permite consultar información adicional dentro de la página sin exigir una decisión modal. Un tooltip, un menú contextual o una lista de opciones requieren su propio patrón: no son cuadros de diálogo.
Compatibilidad y fuentes
Datos consultados el 4 de octubre de 2026. Las primeras versiones registradas por MDN se distinguen por función:
| Función | Chrome | Firefox | Safari |
|---|---|---|---|
<dialog>, show(), showModal() y close() | 37 | 98 | 15.4 |
requestClose() | 134 | 139 | 18.4 |
closedby | 134 | 141 | Solo versión de prueba registrada (preview). |
command y commandfor | 135 | 144 | 26.2 |
command="request-close" | 139 | 144 | 26.2 |
La tabla procede de los datos de compatibilidad de dialog, de HTMLDialogElement y de button. No presupongas soporte de las novedades por reconocer el elemento. La guía separa la sintaxis vigente del HTML Living Standard de esa compatibilidad y ofrece un ejemplo que no exige las funciones más recientes.