details, summary y dialog en HTML
En esta lección
Durante años, para hacer un acordeón, una ventana modal o un globo de ayuda había que escribir bastante JavaScript o cargar una librería. Hoy HTML trae elementos que hacen ese trabajo solos: <details>, <dialog>, el atributo popover y varios elementos para mostrar medidas y resultados. En esta lección aprenderás a usarlos, por qué son mejores que las soluciones caseras y dónde necesitan un poco de ayuda de JavaScript.
La gran ventaja de estos elementos es que ya vienen accesibles: funcionan con el teclado, los lectores de pantalla saben qué son y se comportan igual en todos los navegadores modernos.
Acordeones con <details> y <summary>
<details> crea un bloque que se abre y se cierra. Su primer hijo, <summary>, es el título que siempre se ve y que hace de botón:
<details>
<summary>Horario de la sala</summary>
<p>De lunes a viernes, de 10:00 a 22:00. Sábados, de 10:00 a 14:00.</p>
</details>
Sin una sola línea de JavaScript tienes:
- Un triángulo que indica si está abierto o cerrado.
- Apertura con clic, con la tecla Intro o con la barra espaciadora.
- Un lector de pantalla que anuncia “contraído” o “expandido”.
Todo lo que va después del <summary> es el contenido oculto. Puede ser cualquier cosa: párrafos, listas, imágenes o incluso un formulario.
El atributo open
Por defecto, <details> empieza cerrado. Si quieres que empiece abierto, añade el atributo booleano open:
<details open>
<summary>Normas de la sala</summary>
<ul>
<li>Usa magnesio en bola o líquido.</li>
<li>No escales encima de otra persona.</li>
</ul>
</details>
El navegador añade o quita open cada vez que el usuario lo pulsa. Desde JavaScript puedes leerlo o cambiarlo con la propiedad open y reaccionar con el evento toggle:
const horario = document.querySelector('details');
horario.addEventListener('toggle', () => {
console.log(horario.open ? 'Abierto' : 'Cerrado');
});
Acordeón exclusivo con name
En unas preguntas frecuentes a menudo quieres que solo haya una respuesta abierta a la vez. Basta con dar el mismo name a varios <details>:
<details name="faq">
<summary>¿Hay aparcamiento?</summary>
<p>Sí, gratuito para socios.</p>
</details>
<details name="faq">
<summary>¿Puedo ir con niños?</summary>
<p>Sí, a partir de 6 años y acompañados.</p>
</details>
Al abrir uno, el navegador cierra el que estuviera abierto dentro del mismo grupo. Funciona como los botones de opción (radio) de un formulario, que también se agrupan por name.
Cuidado: no metas contenido imprescindible dentro de un
<details>cerrado. La búsqueda del navegador (Ctrl + F) sí lo encuentra y lo abre, pero mucha gente no pulsará nunca el título.
Ventanas con <dialog>
<dialog> representa un cuadro de diálogo: una confirmación, un aviso, un pequeño formulario. Por defecto está oculto.
<dialog id="aviso">
<p>La sala cierra el 15 de agosto por mantenimiento.</p>
<button type="button" id="cerrar">Entendido</button>
</dialog>
Abrirlo y cerrarlo
Hay dos formas de abrirlo, y la diferencia es importante:
| Forma | Qué hace |
|---|---|
Atributo open o método show() | Lo muestra sin bloquear el resto de la página |
Método showModal() | Lo muestra como modal: encima de todo, con fondo y bloqueando lo demás |
Casi siempre querrás la versión modal, y para eso necesitas un poco de JavaScript:
const aviso = document.querySelector('#aviso');
document.querySelector('#abrir').addEventListener('click', () => aviso.showModal());
document.querySelector('#cerrar').addEventListener('click', () => aviso.close());
Un diálogo modal abierto con showModal() te regala varias cosas:
- Se coloca por encima de todo lo demás, sin pelearte con
z-index. - El resto de la página queda inerte: no se puede pulsar ni enfocar.
- El foco pasa al diálogo, y al cerrarlo vuelve al botón que lo abrió.
- La tecla Esc lo cierra.
Formularios con method="dialog"
Dentro de un <dialog>, un formulario con method="dialog" no envía nada al servidor: simplemente cierra el diálogo. Además, el value del botón pulsado queda guardado en dialog.returnValue:
<dialog id="baja">
<form method="dialog">
<p>¿Seguro que quieres cancelar tu reserva?</p>
<button value="no">No, mantenerla</button>
<button value="si">Sí, cancelar</button>
</form>
</dialog>
const baja = document.querySelector('#baja');
baja.addEventListener('close', () => {
if (baja.returnValue === 'si') {
console.log('Reserva cancelada');
}
});
Así te ahorras un manejador de clic por cada botón.
El fondo: ::backdrop
Cuando el diálogo es modal, detrás aparece una capa que cubre la página. Se le da estilo con el pseudoelemento ::backdrop:
dialog::backdrop {
background: rgb(0 0 0 / 0.6);
backdrop-filter: blur(2px);
}
Consejo: en navegadores actuales también puedes abrir un diálogo sin JavaScript con los invoker commands:
<button commandfor="baja" command="show-modal">. Son recientes, así que comprueba que funcionan en los navegadores que te importen antes de depender de ellos.
Globos y menús con popover
El atributo global popover convierte cualquier elemento en una capa flotante que aparece por encima del resto. Se abre con un botón que lo señala con popovertarget:
<button type="button" popovertarget="info-grados">Ver grados</button>
<div id="info-grados" popover>
<p>Los grados van del 3 (muy fácil) al 9 (élite).</p>
</div>
Sin JavaScript, el botón abre y cierra el popover. Y con el valor por defecto (popover o popover="auto") se cierra solo al pulsar fuera o al pulsar Esc. A eso se le llama light dismiss.
| Valor | Comportamiento |
|---|---|
popover o popover="auto" | Se cierra al pulsar fuera o con Esc; abrir otro cierra el anterior |
popover="manual" | Solo se cierra con su botón o desde JavaScript |
Con popovertargetaction decides qué hace el botón: toggle (por defecto), show o hide. Desde JavaScript tienes showPopover(), hidePopover() y togglePopover().
¿popover o <dialog>?
<dialog>modal: cuando la persona debe responder antes de seguir (confirmar, rellenar algo).popover: para contenido ligero que no bloquea: menús desplegables, globos de ayuda, avisos que se descartan.
Mostrar medidas: <meter>, <progress> y <output>
<meter>: un valor dentro de un rango
<meter> muestra una medida conocida: el espacio usado del disco, las plazas ocupadas, la nota de un examen.
<meter value="9" min="0" max="12" low="6" high="10" optimum="3">9 de 12</meter>
minymaxmarcan el rango.lowyhighdefinen las zonas baja y alta.optimumdice qué zona es la buena. El navegador colorea la barra (verde, amarillo o rojo) según lo cerca que esté el valor de ese óptimo.
El texto de dentro solo se ve en navegadores muy antiguos, pero conviene ponerlo.
<progress>: cuánto falta para terminar
<progress> indica el avance de una tarea: una subida, una descarga, los pasos de un formulario.
<progress value="3" max="5">Paso 3 de 5</progress>
<!-- Sin value: progreso indeterminado (una barra que se mueve) -->
<progress>Cargando...</progress>
La diferencia con <meter> es de significado: <progress> es para algo que avanza hacia un final; <meter>, para una medida estática.
<output>: el resultado de un cálculo
<output> contiene el resultado de una operación hecha con los campos de un formulario. Su atributo for indica de qué campos depende:
<form id="cuota">
<label for="meses">Meses</label>
<input type="number" id="meses" value="3" min="1">
<p>Total: <output id="total" for="meses">105</output> €</p>
</form>
<script>
const meses = document.querySelector('#meses');
const total = document.querySelector('#total');
meses.addEventListener('input', () => {
total.value = meses.value * 35;
});
</script>
Los lectores de pantalla anuncian los cambios de un <output> automáticamente, algo que con un <span> tendrías que programar tú.
Errores frecuentes
- Hacer un acordeón con
<div>y JavaScript. Te olvidarás del teclado o del lector de pantalla. Usa<details>. - Abrir un modal poniendo
openen el<dialog>. Así no es modal: no bloquea la página ni cierra con Esc. UsashowModal(). - Poner
<summary>en otro sitio. Debe ser el primer hijo de<details>. - Botones sin
typedentro de un formulario normal. Un<button>sintypeenvía el formulario. Para abrir diálogos o popovers, usatype="button". - Confundir
<meter>y<progress>. Una batería al 80 % es un<meter>; una descarga al 80 %, un<progress>. - Olvidar el
id.popovertargetapunta aliddel popover; si no coinciden, el botón no hace nada.
Resumen
| Elemento o atributo | Para qué sirve |
|---|---|
<details> + <summary> | Bloque desplegable (acordeón) |
open en <details> | Empieza abierto |
name en varios <details> | Solo uno abierto a la vez |
<dialog> + showModal() | Ventana modal accesible |
method="dialog" | Cierra el diálogo y guarda el value en returnValue |
::backdrop | Fondo detrás del modal |
popover + popovertarget | Capa flotante sin JavaScript |
<meter> | Medida dentro de un rango |
<progress> | Avance de una tarea |
<output> | Resultado de un cálculo |
En la próxima lección verás cómo dibujar gráficos e iconos con SVG y canvas. Y si quieres profundizar en el manejo de eventos que has visto aquí, repasa eventos en JavaScript.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.