- El elemento HTML `` crea cuadros de diálogo modales y no modales nativos del navegador; los modales ponen el resto de la página en estado
inert para bloquear la interacción
- Las formas básicas de abrir y cerrar son
showModal(), show() y close(); también es posible el control declarativo con ``, la Invoker Commands API y la Popover API
- El atributo
closedby divide las formas en que el usuario puede cerrar el cuadro de diálogo en any, closerequest y none, y el comportamiento predeterminado cambia según cómo se haya abierto
- En accesibilidad, son importantes el foco inicial y un botón de cierre explícito; los cuadros de diálogo abiertos con
showModal() ofrecen por defecto el primer elemento enfocable y cierre con la tecla Esc
- Para el estilo se usan
:modal, :open y ::backdrop; para animaciones se requiere manejo de transiciones discretas como display, overlay, @starting-style y transition-behavior: allow-discrete
Rol de `` y comportamiento modal
- El elemento HTML `` sirve para crear cuadros de diálogo modales y no modales
- Un cuadro de diálogo modal impide la interacción con otros elementos de la UI y pone el resto de la página en estado
inert
- Un cuadro de diálogo no modal permite seguir interactuando con el resto de la página mientras está abierto
Estado abierto y política de cierre
- `` incluye atributos globales, pero no debe usarse el atributo
tabindex
- `` en sí no es un elemento interactivo y no recibe foco
- El contenido interno y el botón de cierre sí pueden recibir foco e interactuar
- El atributo
open indica que el cuadro de diálogo está activo y puede recibir interacción
- Si no tiene el atributo
open, no es visible para el usuario
- Para mostrar un cuadro de diálogo, se recomienda usar
.show() o .showModal() en lugar del atributo open
- Un `` abierto con el atributo
open es no modal
- Aunque se puede cambiar el estado abierto/cerrado de un cuadro de diálogo no modal alternando el atributo
open, no se recomienda
- El atributo
closedby especifica de qué formas puede el usuario cerrar el cuadro de diálogo
any: se puede cerrar de las tres formas
- light dismiss, como hacer clic o tocar fuera del cuadro de diálogo
- Acciones específicas de la plataforma, como la tecla Esc, volver atrás en móviles o un gesto de descarte
- Formas definidas por el desarrollador, como un botón que llama a
HTMLDialogElement.close() o el envío de ``
closerequest: se puede cerrar mediante acciones específicas de la plataforma y formas definidas por el desarrollador
none: solo se puede cerrar mediante formas definidas por el desarrollador
- Si no hay un valor válido de
closedby, cuando se abre con showModal() se comporta como closerequest; en los demás casos, como none
JavaScript y control declarativo
- Con JavaScript se puede controlar directamente la visualización y el cierre de ``
showModal(): muestra un cuadro de diálogo modal
show(): muestra un cuadro de diálogo no modal
close(): cierra el cuadro de diálogo
- También se puede cerrar enviando un
dentro de con method="dialog"
- Los cuadros de diálogo modales también pueden cerrarse con la tecla Esc
- La Invoker Commands API permite abrir y cerrar cuadros de diálogo modales solo con atributos de botones
- Se especifican los atributos
commandfor y command en ``
- Los comandos disponibles para un cuadro de diálogo son
"show-modal", "close" y "request-close"
Open dialog
This dialog was opened using an invoker command.
Close
- La Popover API permite abrir, cerrar y alternar declarativamente cuadros de diálogo no modales
- Se agrega el atributo
popover a `` para convertirlo en popover
- Se especifican
popovertarget y popovertargetaction en botones o elementos de entrada
- Como los cuadros de diálogo popover son no modales, se pueden cerrar haciendo clic fuera
- Si no se especifica un valor para
popover, se usa el valor predeterminado "auto", que activa light dismiss
popover="manual" desactiva light dismiss y requiere una forma aparte, como un botón de cierre
- Si se omite
popovertargetaction, se usa el valor predeterminado toggle
Envío de formularios y manejo del cierre
- Todo `` necesita un mecanismo de cierre, y debe funcionar también en dispositivos sin teclado físico
- Hay varias formas de cierre
- Configurar
method="dialog" en un dentro de y enviarlo
- Hacer clic fuera del cuadro de diálogo cuando light dismiss está activado
- Presionar la tecla Esc en un cuadro de diálogo que lo permita
- Llamar a
HTMLDialogElement.close()
formmethod="dialog" en `` o en un botón de envío cierra el cuadro de diálogo
- El estado de los controles del formulario se conserva, pero no se envía
returnValue se establece con el valor del botón activado
- En formularios con campos obligatorios, el user agent impide cerrar el cuadro de diálogo mediante un envío normal hasta que se proporcionen los valores
- Usar
formnovalidate en el botón de cierre permite omitir la validación del formulario
- También se puede cerrar llamando a
dialog.close() desde JavaScript
Foco y accesibilidad
- Al abrir `` con
showModal(), por defecto el foco se establece en el primer elemento enfocable anidado
- Para especificar la posición de foco inicial más adecuada en un cuadro de diálogo concreto, se puede usar el atributo
autofocus
- Si no hay un elemento para interactuar de inmediato, se recomienda poner
autofocus en el botón de cierre
- Si la posición de foco inicial es incierta, como en un cuadro de diálogo renderizado dinámicamente, `` en sí puede ser una posición de foco inicial adecuada
- La forma más robusta es incluir botones explícitos como confirmar, cancelar o cerrar para que todos los usuarios puedan cerrarlo
- Los cuadros de diálogo invocados con
showModal() se cierran por defecto con la tecla Esc
- Los cuadros de diálogo no modales no se cierran por defecto con la tecla Esc
- Los usuarios de teclado esperan que un cuadro de diálogo modal se cierre con la tecla Esc
- Si hay varios cuadros de diálogo modales abiertos, la tecla Esc debe cerrar solo el último que se mostró; al usar ``, el navegador ofrece este comportamiento
- El `` nativo ofrece funciones de usabilidad y accesibilidad que habría que replicar manualmente en cuadros de diálogo personalizados creados con otros elementos
- El navegador expone `` de manera similar a un cuadro de diálogo personalizado que usa ARIA
role="dialog"
- Un `` invocado con
showModal() tiene implícitamente aria-modal="true"
- Un `` mostrado con
show(), el atributo open o cambiando el display predeterminado se expone como aria-modal="false"
- Al implementar un cuadro de diálogo modal, todo lo que esté fuera de `` y su contenido debe renderizarse como
inert; al usar showModal(), el navegador ofrece este comportamiento
Estilos CSS y backdrop
- `` se puede seleccionar por nombre de elemento como un elemento normal
- Para estilos basados en estado se pueden usar las pseudoclases
:modal y :open
- El fondo de un cuadro de diálogo modal puede estilizarse con el pseudoelemento
::backdrop
- Se aplica al backdrop que aparece detrás de `` cuando el cuadro de diálogo se muestra con
showModal()
- Puede desenfocar, oscurecer u ocultar el contenido posterior que está en estado inert
Patrones de uso con ejemplos
- El ejemplo de Invoker Commands API abre el cuadro de diálogo con un botón
command="show-modal" y lo cierra con un botón command="close"
- Se puede abrir con el botón “Open dialog”
- Se puede cerrar con el botón “Close” o con la tecla Esc
- El ejemplo de Popover API abre y cierra un cuadro de diálogo no modal con
popover, popovertarget y popovertargetaction
- Se puede cerrar con el botón “Close”, la tecla Esc o seleccionando fuera del cuadro de diálogo
- Si se usa
popover="manual", light dismiss queda desactivado
- El ejemplo del atributo
open crea un cuadro de diálogo no modal solo con HTML que ya está abierto al cargar la página
- Se puede cerrar con el botón “OK” de ``
- No ofrece una forma de volver a abrirlo después de cerrarlo
- Para mostrar cuadros de diálogo no modales, se prefiere usar
HTMLDialogElement.show()
- El ejemplo de cuadro de diálogo modal se abre con
.showModal() y se cierra con close()
- Estiliza un fondo con degradado usando
::backdrop
- Cuando el cuadro de diálogo está abierto, el exterior queda en estado inert y no se puede interactuar con el documento
- El ejemplo de
returnValue maneja el valor de retorno del cuadro de diálogo con un formulario y valores de botones
- El
returnValue predeterminado es una cadena vacía o el valor del botón que envió el formulario dentro del cuadro de diálogo
- El botón “Confirm” pasa el valor seleccionado a
close() para usarlo como valor de retorno
- Si se cierra con la tecla Esc,
returnValue no se actualiza y tampoco se dispara el evento close, por lo que el texto de `` no se actualiza
Animaciones de ``
- Un
oculto tiene `display: none`; un mostrado tiene display: block
- Cuando cambia el estado de visualización, se agrega o elimina de la top layer y del árbol de accesibilidad
- Para animar ``, la propiedad
display debe ser animable
- Los navegadores compatibles tratan
display como una animación discreta
- Al cambiar de
none a block, pasa a block en el punto 0%, por lo que se ve durante toda la animación
- Al cambiar de
block a none, pasa a none en el punto 100%, por lo que se ve durante toda la animación
-
Animación con CSS transition
- Para animar `` con CSS transition se necesitan las siguientes funciones
@starting-style: proporciona los valores iniciales de transición cada vez que se abre el cuadro de diálogo
- transición de
display: mantiene el cuadro de diálogo con un valor de display visible durante la transición
- transición de
overlay: retrasa la eliminación de la top layer hasta que termine la transición
transition-behavior: allow-discrete: activa transiciones discretas de display y overlay, que por defecto no se animan
- En navegadores que no admiten la pseudoclase
:open, se puede usar el selector de atributo dialog[open] para estilizar el estado abierto
- Como `` cambia de
display: none a display: block cada vez que se muestra, en cada transición de entrada se pasa desde @starting-style al estilo dialog:open
-
Animación con keyframes
- Las animaciones CSS con keyframes tienen restricciones distintas a las transition
- No proporcionan
@starting-style
- Incluyen el valor de
display dentro de los keyframes
- No hace falta activar explícitamente
allow-discrete
- Tampoco hace falta configurar
overlay en los keyframes
- El fade-out del backdrop no puede animarse porque, cuando el cuadro de diálogo se cierra, el backdrop se elimina inmediatamente del DOM
Resumen técnico
- Categoría de contenido: flow content, sectioning root
- Contenido permitido: flow content
- Las etiquetas de inicio y cierre son obligatorias; no se omiten etiquetas
- Padres permitidos: cualquier elemento que acepte flow content
- Rol ARIA implícito:
dialog
- Rol ARIA permitido:
alertdialog
- Interfaz DOM:
HTMLDialogElement
- Especificación: HTML # the-dialog-element
1 comentarios
Opiniones en Hacker News
Entre los elementos HTML interactivos están el selector de archivos, selector de color, selector de fecha/hora, slider numérico, opciones sugeridas en campos de texto, resumen/detalle desplegable, FAQ y reproductores multimedia con controles.
Estos elementos son buenos porque son ligeros y semánticos, y los
<details>que comparten el mismonameincluso pueden comportarse de forma que, al abrir uno, se cierre el elemento anterior.Ojalá estuvieran mejor implementados.
Uso
<dialog>desde 2019 y, aunque Firefox y Safari tardaron varios años más en soportarlo, el polyfill de Google era de buena calidad, así que lo usamos sin problemas en producción para un SaaS empresarial.Lo que más decepciona es que el elemento casi no venía estilizado: apenas un borde negro grueso de píxeles y esquinas rectas, muy distinto de lo que esperaba, que era que el navegador siguiera las convenciones del sistema operativo para diálogos/ventanas y lo hiciera verse como un diálogo nativo de macOS, iOS o Windows.
Como el elemento abierto está en una capa superior separada, también esperaba que pudiera salirse del viewport del navegador, pero esa expectativa tampoco se cumplió. Desde el punto de vista del usuario, nadie quiere un popup inmóvil o un diálogo modal que cubra por completo el contenido del documento.
Parece que los fabricantes de navegadores no quieren que usemos
alert(),prompt()yconfirm()porque bloquean JavaScript/el hilo principal, pero no han ofrecido una API alternativa igual de simple, efectiva y que no exija habilidades de diseño de UI. En vez de intentar convencernos de que<dialog>es mejor en todos los casos, estaría bien que ofrecieran una API no bloqueante basada en Promise para alert/prompt/confirm.Que las nuevas funciones no se comporten como la vieja familia de
alert()no es solo una decepción accidental; también tiene ventajas.Aun así, hacía falta prestar un poco más de atención al estilo por defecto de
<dialog>; aunque no se viera ni se comportara al 100% como un diálogo del sistema fuera del DOM, habría sido mucho mejor con un estilo predeterminado que al menos encajara con el estilo base del navegador.En el caso de apps web con más permisos que una página común, como las PWA, esta dirección podría ser más aceptable, igual que funciones separadas para estilizar ventanas o interactuar con el sistema.
<dialog>se vea como una ventana nativa del navegador, podrían difuminar la line of death.A un sitio malicioso le resultaría más fácil mostrar un popup de “actualización del navegador” en medio de la página y redirigir a una página de descarga de Chrome convincente para que el usuario baje un ejecutable modificado.
https://textslashplain.com/2017/01/14/the-line-of-death/
Los usuarios suelen tener varias pestañas abiertas, y quizá en otra pestaña haya información necesaria para completar el diálogo.
También hay que tener mucho cuidado con cuánto control visual se le da a un diálogo real. En especial si se lo hace parecer al sistema operativo: incluso hoy mucha gente cae en alertas falsas de virus del navegador, y un diálogo que se vea auténtico y con el que sea obligatorio interactuar podría aumentar mucho la tasa de éxito de los ataques.
La mayoría de las empresas donde trabajé tenían algún tipo de lenguaje de diseño coherente y, diga lo que diga la gente que prefiere HTML/CSS puro, usar los componentes integrados del navegador tal cual casi siempre se veía tosco y amateur.
Si este problema no se resuelve, será difícil que se usen de forma masiva.
alert(),prompt()yconfirm()que funcionen comoawait Prompts.alert("This is an alert message!");,await Prompts.confirm(...),await Prompts.prompt(...).Demo: https://tools.simonwillison.net/prompts-js
El código lo escribió o1: https://chatgpt.com/share/67539c28-4df0-8006-b021-4f468e011f...
Escribí un artículo llamado “The HTML dialog element API is a mess”: https://lapcatsoftware.com/articles/2024/2/1.html
Aun así, su valor está en que son estándares. La web es genial porque funciona en todas partes, y por eso inevitablemente es desordenada.
En este contexto, creo que
<dialog>es un éxito, sobre todo para herramientas internas de administración. No me importan las modas modernas del frontend; solo quiero ahorrar espacio en pantalla y mostrar contenido como una superposición modal encima de la vista principal.Para abrir un cuadro de diálogo se necesita una entrada del usuario, y un cuadro de diálogo modal bloquea la entrada del usuario, así que para que eso ocurra, una entrada dentro del cuadro tendría que volver a abrir ese mismo cuadro.
Si se abre un cuadro de diálogo mediante una operación asíncrona, hay que llevar registro de qué está abierto y qué está cerrado, y en entornos como Qt pasa algo parecido.
<dialog>se convirtiera en una versión potenciada de await confirm/prompt, personalizable y sin advertencias distintas según el navegador al abrirse varias veces.Pero en realidad se parece más a un
divembellecido.La Compression Streams API no estaba mal, pero era una API muy pequeña.
Viendo el patrón, parece que Google no maneja muy bien la experiencia de usuario ni la experiencia de desarrollador.
Además, al buscar la postura de los estándares, vi que ambos soportan URLPattern.
Independientemente de la implementación, creo que fue un paso en la dirección correcta.
También hay una propuesta en curso que parece una mejora de
<select>: https://open-ui.org/components/combobox.explainer/Para notificaciones tipo toast, el navegador ya incluye la Popover API: https://mdn.github.io/dom-examples/popover-api/
También hay una propuesta de popover hint para tooltips: https://open-ui.org/components/popover-hint.research.explain...
Me gusta el elemento
<dialog>, especialmente por las consideraciones de accesibilidad integradas y estandarizadas.Espero el día en que pueda usarlo sin polyfill cuando Safari anterior a 15.4 quede fuera de los criterios de soporte.
Dicho eso, mi mayor queja es su dependencia de JavaScript. Casi todos los visitantes de un sitio vendrán con JavaScript activado, pero también da cierta satisfacción hacer que no sea estrictamente necesario.
No entiendo por qué no se puede controlar el estado abierto de un cuadro de diálogo con CSS o con un botón objetivo, y me gustaría saberlo si estoy equivocado.
popover="".Se puede apuntar fácilmente con un botón, no requiere JavaScript del lado del cliente y también puede cerrarse al hacer clic afuera. Es un comportamiento que
<dialog>no tiene por defecto.No estoy seguro de la accesibilidad, pero creo que no es peor que las formas antiguas hechas con etiquetas, checkboxes y elementos de formulario ocultos; además es mucho más simple y se siente menos como un hack.
https://css-tricks.com/invoker-commands-additional-ways-to-w...
Me gustaría que HTML soportara el concepto de una etiqueta tipo
<page>, que permitiera definir varias páginas dentro de un mismo archivo y mostrar una a la vez.Que no se viera como un cuadro de diálogo, y que cada PAGE pudiera traer secciones comunes dentro de la misma página, como header, sidebar y footer, según cuál esté seleccionada.
Hoy se puede hacer ocultando y mostrando
div, pero podría ser útil que una etiqueta HTML específica soportara este enfoque.Hoy, al usarlo, tuve un problema que no pude esquivar.
Si hay un formulario dentro del cuadro de diálogo, al presionar Enter con foco en un input o Space con foco en el botón de envío, el cuadro de diálogo se cierra junto con el envío del formulario.
No encontré una forma limpia de impedirlo. Normalmente un formulario recarga la página, así que quizá no sea un problema común, pero en este caso estaba usando htmx.
Uso un editor que actualiza un iframe mediante un formulario dentro de
<dialog>, y solo se recarga el iframe de destino; el cuadro de diálogo no se cierra.Tampoco pude reproducir que el cuadro de diálogo se cierre al presionar Enter. Debería ser igual que
submit, ysubmittampoco cierra el cuadro de diálogo.Por cierto, ayer aprendí que un botón sí puede cerrar un cuadro de diálogo.
preventDefaultystopPropagation?Mientras buscaba un gestor de consentimiento de cookies para incluir en una build recién optimizada, no me gustó que las opciones open source superaran los 100 KB, así que hice uno propio: https://github.com/replete/biscuitman
Intenté hacerlo lo más pequeño posible apoyándome en
<dialog>, y con solo unas pocas reglas CSS funciona de forma nativa incluso sin estilos.Al final también terminé escribiendo una herramienta de build que compila hasta IE11 y versiones de navegadores realmente antiguas.
<dialog>funciona bastante bien en general, y en navegadores viejos requiere algunos trucos de CSS, pero no es difícil de manejar. Es una buena incorporación a la plataforma web, aunque después de 20 años haciendo esto, no quiero volver a crear un control personalizado de selección múltiple cada pocos años. Los controles nativos son buenos.El comportamiento típico de cierre de la mayoría de los ejemplos no funciona en Android Firefox
autofocusdel botón de cierre no funcionabaAl final, cada vez que llamaba a
show(), agregué una línea como$('#thatModal *[autofocus]').focus()Según MDN, debería funcionar de forma predeterminada como se espera
Parece que solo funcionan correctamente los listeners agregados con JavaScript