Saltar al contenido
elementos-interactivos.html · devschool

details, summary y dialog en HTML

Lección 18 de 23 · 8 min de lectura · Actualizado el

En esta lección
  1. Acordeones con <details> y <summary>
  2. Ventanas con <dialog>
  3. Globos y menús con popover
  4. Mostrar medidas: <meter>, <progress> y <output>
  5. Errores frecuentes
  6. Resumen

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:

FormaQué 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.

ValorComportamiento
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>
  • min y max marcan el rango.
  • low y high definen las zonas baja y alta.
  • optimum dice 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 open en el <dialog>. Así no es modal: no bloquea la página ni cierra con Esc. Usa showModal().
  • Poner <summary> en otro sitio. Debe ser el primer hijo de <details>.
  • Botones sin type dentro de un formulario normal. Un <button> sin type envía el formulario. Para abrir diálogos o popovers, usa type="button".
  • Confundir <meter> y <progress>. Una batería al 80 % es un <meter>; una descarga al 80 %, un <progress>.
  • Olvidar el id. popovertarget apunta al id del popover; si no coinciden, el botón no hace nada.

Resumen

Elemento o atributoPara 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
::backdropFondo detrás del modal
popover + popovertargetCapa 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).

elementos-interactivos.html
Resultado

Pon a prueba lo que has aprendido

[HTML] Tienes cinco <details> de preguntas frecuentes y quieres que solo pueda estar abierto uno a la vez. ¿Qué haces?

[HTML] ¿Qué ocurre al pulsar el botón Borrar?
<dialog id="confirmar">
  <form method="dialog">
    <button value="cancelar">Cancelar</button>
    <button value="borrar">Borrar</button>
  </form>
</dialog>

[HTML] ¿Qué diferencia hay entre abrir un <dialog> con show() y con showModal()?

[HTML] Quieres mostrar el nivel de batería de un móvil (80 %). ¿Qué elemento es el adecuado?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.