Aprendé cómo construir un elemento desplegable desde cero de manera fácil y rápida sin usar WAI-ARIA ni Javascript, solo HTML
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 atributonamecon 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ódigoen 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 essummary
| Key | Action |
|---|---|
| Tab | Mueve el foco al elemento desplegable o al siguiente elemento interactivo |
| Shift + Tab | Mueve el foco al elemento interactivo previo |
| Espacio o Tab | Expande/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!
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 mismoname. Este grupo de acordeones usaname="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.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.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.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
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
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.
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.
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
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
Este caso de uso usa el elemento HTMLindex.html4 added- <details>
- <summary>Show more</summary>
- <p>Everything before or after the summary is part of the content.</p>
- </details>
<ins>para insertar código nuevo. Podés leer más sobre este elemento acá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.