Navegación mobile

Ver todas las publicaciones

Aprendé cómo construir un elemento desplegable desde cero de manera fácil y rápida sin usar WAI-ARIA ni Javascript, solo HTML

Holaaa! Acá Mica al teclado, la de carne y hueso y no una IA. No tengo nada en contra, ponele, pero creo que la escritura y la producción son el último bastión de humanidad así que bueno, todavía no automticé esta tarea y espero no hacerlo. Así que si estás leyendo esto y sos humano, al final del post vas a encontrar los medios por donde contactarme si tenés alguna duda! Antes de empezar, Is there anybody out there?

Introducción

Hola mundo! Hace dos años hice un post sobre cómo utilizar la etiqueta HTML <details> para hacer un elemento desplegable accesible y funcional sin necesidad de usar Javascript ni WAI-ARIA. Si bien siento que el post era bastante completo, el año pasado (2025) se introdujo un nuevo pseudo-elemento en CSS llamado ::details-content para seguir simplificando la construcción del contenido a expandir y sentí la necesidad de actualizar el post, expandir el testeo en las diferentes tecnologías asistivas y además agregar casos de uso. Pasen y vean!

Requerimientos para que un elemento desplegable sea funcional

Para que un elemento desplegable sea funcional y accesible, los siguientes requerimientos deben ser tenidos en cuenta (obligatorios):

  • Cuando el foco está en el "header" (<summary>), se debe poder expandir o colapsar el contenido al presionar la tecla Enter o Espacio.
  • Cuando se presiona la tecla Tab, el foco se debe mover al siguiente elemento interactivo que puede estar dentro del contenido de <details> o puede ser el siguiente elemento interactivo en la página.
  • Cuando se presiona la combinación de teclas Shift + Tab, el foco se mueve al elemento interactivo anterior a <details>.
  • Los lectores de pantalla deben anunciar el rol del elemento, el estado (si está expandido o colapsado) y también su nombre accesible es decir, lo que vayas a poner en el <summary>.

De forma nativa, el elemento <details> nos facilita todos estos requerimientos así que A USARLO.

Anatomía del elemento details

<details>

Elemento padre. Acá se va a envolver toda la funcionalidad y van a vivir el elemento <summary> y el pseudo-elemento de CSS ::details-content.

<summary>

Primer descendiente del elemento <details>. Este elemento va a lucir como un header pero va a ser el responsable de expandir o colapsar el contenido. El elemento <summary> acepta como contenido cualquiera de los siguientes tags: <h1>-<h6> y phrasing content.

::marker

Ícono nativo que viene en el elemento <details>. Como vamos a ver más adelante, podemos editarlo con CSS.

::details-content

Segundo descendiente del elemento <details> y puede contener cualquier elemento válido de HTML ya que va a funcionar como un contenedor. La mejor parte de este contenedor es que no vamos a necesitar de un div o de un Fragment para generarlo!

Atributos

open

Este atributo va a indicarles a las tecnologías el estado del elemento desplegable es decir, si está expandido (open) o colapsado (open="false").

  • Este atributo viene de manera implícita en el elemento. No hay necesidad de utilizarlo si querés que el elemento esté colapsado por default.
  • Si solo se coloca un simple open, el elemento va a estar expandido por default.
name

Este atributo solo va a ser necesario cuando tenemos un grupo de elementos <details> para manejar es decir, cuando tenemos un acordeón. Todos los elementos <details> van a tener que tener un atributo name con el mismo valor y esto va a permitir que cuando uno esté expandido y al mismo tiempo se expanda otro elemento <details> dentro del acordeón, el elemento acordeón previamente expandido se va a colapsar automáticamente. Si querés jugar un poco con esta funcionalidad podés ir a este caso de uso

Estrategias CSS para estilar un elemento desplegable

Cómo estilar el elemento summary

El elemento <summary> viene por defecto con la propiedad de CSS display: list-item. Esta propiedad hace que nativamente aparezca un ícono en el lado izquierdo del <summary> que puede ser un triángulo, un círculo o también podés usar la at-rule de CSS @counter-style para definir una lista de íconos personalizados. Por otro lado, este ícono se puede estilar con el pseudo-elemento ::marker de la siguiente manera:

::marker –– Código

::marker {
  color: orangered;
  font-size: 2rem;
}

::marker –– Ejemplo funcional

ícono desplegable estilado

El pseudo-elemento ::marker nos permite estilar el ícono nativo del elemento desplegable.

Buenísimo, ahora que ya sabemos que el ícono nativo del elemento desplegable se puede estilar tenemos que aprender cómo ocultarlo. Como mencioné más arriba, por defecto este elemento viene con la propiedad display: list-item de CSS, por lo que simplemente agregando una propiedad display: flex en el selector details > summary el ícono nativo queda omitido automáticamente. Esta estrategia es válida, pero no hay que olvidarse de que sí o sí tenemos que proporcionar una representación visual que demuestre que explícitamente es un elemento desplegable y, por ende, interactivo. Este ícono custom tiene que ir con un atributo aria-hidden="true" porque los usuarios de tecnologías asistivas ya de por sí van a percibir el rol, el estado y el nombre accesible.

Remover el ícono nativo del elemento desplegable –– Código

details > summary {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}

Remover el ícono nativo del elemento desplegable –– Ejemplo funcional

Sin el ícono nativo

Como se explicó arriba, el ícono nativo se esconde automáticamente gracias al uso de la propiedad display: flex y también se coloca un ícono decorativo y customizado para dar a entender que es un elemento desplegable.

Por último, me gustaría comentar que también existe un pseudo-elemento de CSS muy conocido llamado ::-webkit-details-marker para browsers basados en WebKit para poder estilar el marker pero, la verdad es que HOY quedó vetusto gracias a que ya la mayoría de los browsers ya soportan el pseudo-selector ::marker. Pueden usarlo, va a funcionar pero el ::marker simplifica la vida.

Adiós al uso de div para estilar el contenido expandido

En septiembre del 2025 se estandarizó el uso del pseudo-elemento ::details-content que genera un contenedor para estilar el contenido expandido del elemento <details>. Esto es una buena noticia ya que vamos a poder jubilar el uso de divs para estilar contenedores.

Expandir/Colapsar el contenido –– CSS código

details::details-content {
  block-size: 0;
  overflow: clip;
}

details[open]::details-content {
  padding: 0.85rem;
}

@media (prefers-reduced-motion: no-preference) {
  details::details-content {
    transition:
      block-size 0.3s ease,
      padding-block 0.3s ease,
      content-visibility 0.3s ease allow-discrete;
  }
}
  • details::details-content, estila el contenido cuando el elemento está colapsado.
  • details[open]::details-content, estila el contenido cuando el elemento está expandido. También va a determinar cuándo termina una animación, en caso de que haya.
  • @media (prefers-reduced-motion: no-preference) {details::details-content {}}, mejora de accesibilidad! si el usuario tiene las animaciones activadas en su sistema operativo, en esta funcionalidad van a vivir las animaciones. En caso de que el usuario tenga las animaciones desactivadas en su sistema operativo, las animaciones se omiten.

Expandir/Colapsar el contenido –– Ejemplo funcional

Contenido colapsable con estilos

Todo lo que hay en este panel vive dentro de ::details-content. Se puede estilar como cualquier otro elemento.

Qué se puede estilar

  • Su propio fondo, padding y borde.
  • La tipografía: títulos, énfasis y código en línea.
  • La transición de apertura y cierre.

Más información sobre ::details-content en MDN.

Una estructura semántica correcta se va a renderizar como el siguiente bloque de código. Como se puede ver, no hay div soup ni estructuras raras.

<details>
  <summary>{...}</summary>
  {here starts the ::details-content}
  <p>{...}</p>
  <h4>{...}</h4>
  <ul>
    <li>{...}</li>
    <li>{...}</li>
    <li>{...}</li>
  </ul>
</details>

Navegación con teclado

El elemento que recibe el foco y maneja la funcionalidad es summary
Consola de Chrome DevTools con el selector document.activeElement que apunta al elemento summary demostrando que es el que recibe foco y maneja las funcionalidades de expandir y colapsar
Consola de Chrome DevTools con el selector document.activeElement que apunta al elemento summary demostrando que es el que recibe foco y maneja las funcionalidades de expandir y colapsar
KeyAction
TabMueve el foco al elemento desplegable o al siguiente elemento interactivo
Shift + TabMueve el foco al elemento interactivo previo
Espacio o TabExpande/colapsa el elemento desplegable

Lectores de pantalla

Cómo las diferentes combinaciones de browsers, dispositivos y lectores de pantalla anuncian el elemento desplegable <details>. Tengo que aclarar que el testeo con screen readers fue hecho en inglés porque es el idioma que tengo en todos mis sistemas operativos. Si hay alguna duda, me pueden mandar un mensaje a micaela.avigliano@gmail.com! Hecha la aclaración, pueden interactuar con el siguiente elemento desplegable y descubrirlo:

¿Cómo los lectores de pantalla anuncian el tag details?
  • VoiceOver macOS Tahoe 26.5.2 + Safari 26.5.2 (21624.2.5.11.8):
    • Colapsado: 'How do screen readers announce the details tag?, collapsed, summary'
    • Expanded: 'How do screen readers announced the details tag?, expanded, summary'
    • Toggle state with keys combination Control + Option + Space: 'collapsed' or 'expanded'
  • VoiceOver macOS Tahoe 26.5.2 + Chrome 150.0.7871.184:
    • Collapsed: 'How do screen readers announce the details tag?, collapsed, disclosure triangle, group'
    • Expanded: 'How do screen readers announce the details tag?, expanded, disclosure triangle, group'
    • Toggle state with keys combination Control + Option + Space: if it is collapsed, the whole thing again is announced as collapsed and if it is expanded, the whole thing again is announced as expanded
  • VoiceOver macOS Tahoe 26.5.2 + Firefox 152.0.6:
    • same as in the combination VoiceOver macOS Tahoe 26.5.2 + Chrome 150.0.7871.184
  • NVDA 2026.1.1 + Chrome 150.0.7871.18:
    • Collapsed: 'How do screen readers announce the details tag?, button, collapsed'
    • Expanded: 'How do screen readers announce the details tag?, button, expanded'
    • Toggle state with Enter or Space: 'collapsed' or 'expanded'
  • NVDA 2026.1.1 + Firefox 153.0:
    • Collapsed: 'How do screen readers announce the details tag?, button, collapsed'
    • Expanded: 'How do screen readers announce the details tag?, button, expanded'
    • Toggle state with Enter or Space: 'collapsed' or 'expanded'
  • TalkBack, Pixel 10, Android 16 + Chrome 149.0.7827.160:
    • Collapsed: 'collapsed, How do screen readers announce the details tag?, disclosure triangle'
    • Expanded: 'expanded, How do screen readers announce the details tag?, disclosure triangle'
    • Toggle state by double-tapping to activate: 'collapsed' or 'expanded'
  • TalkBack, Pixel 10, Android 16 + Firefox 145.0.2:
    • Collapsed: 'collapsed, How do screen readers announce the details tag?, button, How do screen readers announce the details tag?, Space'
    • Expanded: 'expanded, How do screen readers announce the details tag?, button, How do screen readers announce the details tag?, Space'
    • Toggle state by double-tapping to activate: 'collapsed' or 'expanded'

Casos de uso

Algunas UIs desarrolladas solamente con la etiqueta <details>. Nada de Javascript, ni WAI-ARIAs raros. Disfruten!

  1. Sección de preguntas y respuestas

    En este caso, esta sección va a ser un acordeón porque es un grupo de elementos desplegables que comparten el mismo objetivo.

    ¿Necesito JavaScript para manejar las interacciones de un acordeón/disclosure?

    No. <details> se encarga por su cuenta del estado abierto y cerrado, del manejo por teclado y de los anuncios del lector de pantalla.

    ¿Cómo mantengo un solo panel abierto a la vez?

    Cada <details> el mismo name. Este grupo de acordeones usa name="faq", así que al abrir una pregunta se cierra la anterior.

    ¿El summary puede contener un encabezado?

    Sí. Un <summary> puede envolver un <h2><h6> o cualquier contenido de tipo phrasing, así que tus FAQ pueden seguir formando parte del esquema del documento.

  2. Funcionalidad "Leer más" en la misma línea

    Permite seguir leyendo un contenido y utiliza el pseudo-elemento ::details-content para que el contenido expandido se funda con la introducción.

    En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor.
    Leer másLeer menos Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lentejas los viernes, algún palomino de añadidura los domingos, consumían las tres partes de su hacienda. Don Quijote de la Mancha, Miguel de Cervantes, 1605.
  3. Spoiler o mostrar una respuesta

    Podés esconder o mostrar una respuesta, una solución a un código, un spoiler, etc.

    Cómo sumar un array de números con JavaScript?

    Ver soluciónOcultar solución

    const total = nums.reduce((a, b) => a + b, 0). Recorre el array una sola vez y calcula el total.

  4. Detalles de un pedido

    Sección con información sobre una compra. Lo que siempre está visible es el precio y la información de envío. El desglose del pedido está dentro del elemento desplegable. Esta misma solución se puede usar para especificaciones, información, metadata, etc.

    Pedido #2048

    Enviado. Llega el jue 15 de ago

    38,00 $

    Ver detalles del pedido
    Caja de semillas 1
    32,00$
    Envío rápido
    6,00$
    Transportista
    Tracked 48
    Seguimiento
    00-0000-00
  5. Markers animados

    Otro ejemplo de un acordeón donde podemos ver cómo funciona cuando se comparte o no un mismo atributo name. Por otra parte, se puede ver también cómo los markers se pueden estilar y animar para que no sea un simple chevron aburrido.

    Sin agrupar: cada tarjeta se abre y se cierra por su cuenta.

    • Punto que se expande

      Un punto relleno se expande en un anillo hueco al abrir el panel.

    • Chevron que rota

      Una flecha lateral gira un cuarto de vuelta para apuntar hacia abajo cuando el contenido se expande.

    • Más menos

      Cuando el contenido esta colapsado el ícono es un más pero cuando está expandido es un menos.

  6. Paneles desplegables con filtros

    Grupo de filtros ordenados en elementos desplegables. En este caso, el grupo no va a compartir el atributo name ya que es necesario poder tenerlos todos abiertos a la vez, en caso de ser necesario, para ver todos los filtros.

    Precio
    Dificultad
  7. Especificaciones

    Elemento desplegable donde el usuario va a poder encontrar especificaciones sobre un producto como sus dimensiones, materiales, instrucciones de cuidado, etc.

    Especificaciones completas
    Dimensiones de la caja
    24 × 18 × 6 cm
    Peso
    640 g
    Variedades de semillas
    6 criollas, sin transgénicos
    Envase
    100% compostable
    Vida útil
    Sembrar antes de 18 meses
  8. Elemento desplegable para mostrar código o logs

    Este es un ejemplo muy parecido a cómo Github muestra los cambios en los pull request o los logs en los builds. Algo importante acá es que tanto en mi ejemplo como en la UI de Github NUNCA el elemento summary va a poder contener elementos interactivos y todo elemento que se coloque antes o después del summary pero dentro del elemento details va a ser considerado parte del ::details-content

    index.html
    4 added
    1. <details>
    2. <summary>Show more</summary>
    3. <p>Everything before or after the summary is part of the content.</p>
    4. </details>
    Este caso de uso usa el elemento HTML <ins> para insertar código nuevo. Podés leer más sobre este elemento acá
  9. Emoji en el marker

    Un pequeño y tonto ejemplo de qué tan flexibles y personalizables son los marcadores en la etiqueta details.

    ¿Florece?

    El emoji de brote para cuando details esta colapsado y el emoji de un girasol para cuando details esta expandido.