Acordeón
Bloques que se pliegan y se despliegan, hechos sobre <details> y <summary>. Lo nativo
ya resuelve teclado, semántica y estado, y abre y cierra antes de que cargue el JavaScript. El componente entra solo donde
lo nativo no llega: animar.
¿Necesito un build en mi proyecto?
No. El CSS ya viene compilado y Tailwind se usa solo dentro de la biblioteca. De tu lado son dos archivos y nada más.
¿Funciona con HTMX?
Sí. Lo que llega por swap se inicializa solo, así que el acordeón que viene del servidor ya nace animado.
¿Y si falla el JavaScript?
Este acordeón sigue abriendo y cerrando: es un <details> nativo. Lo único que se pierde es la animación.
Ejemplos
El mismo <details>, con un atributo o una clase de más.
Varios abiertos
Sin data-single, cada elemento abre y cierra por su cuenta.
Datos del contrato
Número 2026-0142, vigencia de 12 meses.
Partes
Tucano Comercial S.L. y Panadería Pan Caliente.
Cláusulas
Ajuste anual por inflación y recargo del 2% por retraso.
Uno a la vez
data-single="true" pliega los demás al abrir uno.
Plan mensual
Cobro cada día 10, cancela cuando quieras.
Plan anual
Dos meses de descuento, pagado de una vez.
A consultar
Para más de 50 usuarios.
Contenido de verdad dentro
La altura la mide el navegador, no se adivina: formulario, tabla e imagen abren igual.
Filtros del informe
Columnas visibles
Sin divisores, para menú
is-plain quita las líneas y deja el título con aspecto de etiqueta de grupo.
Registros
Finanzas
Cómo usar
El HTML es el de siempre. Los <details> deben ser hijos directos del elemento marcado, y el
open escrito en la plantilla indica cuáles nacen abiertos.
<div class="tuc-accordion" data-tuc-accordion data-single="true">
<details open>
<summary>Proyectos</summary>
<p>Listar, crear y seguir inspecciones.</p>
</details>
<details>
<summary>Informes</summary>
<p>Mensual, por equipo y por período.</p>
</details>
</div>El script añade lo que falta: la clase tuc-accordion en el contenedor, tuc-accordion__item en cada
<details>, tuc-accordion__trigger en el <summary>, la flecha, y envuelve el resto del
contenido en tuc-accordion__body y tuc-accordion__content. Hasta entonces, el CSS ya dibuja el acordeón en crudo con
las mismas líneas y el mismo espaciado, para que nada salte cuando llega el JavaScript.
En la plantilla de Django
<div class="tuc-accordion" data-tuc-accordion data-single="true">
{% for question in questions %}
<details{% if forloop.first %} open{% endif %}>
<summary>{{ question.title }}</summary>
{{ question.answer|linebreaks }}
</details>
{% endfor %}
</div>En JavaScript
const faq = new Tucano.Accordion('#faq', { single: true });
const item = document.querySelector('#faq > details:nth-child(2)');
faq.open(item); // con single, pliega los demás
faq.close(item); // anima y solo entonces quita el open
faq.items; // los <details>, en orden
faq.destroy();Cómo anima
La altura del contenido es auto, y auto no tiene transición. Al cerrar, además, el navegador oculta
el contenido en el mismo fotograma en que cae el open.
La apertura es CSS puro: el cuerpo es un grid de una fila, y la fila anima de 0fr a 1fr — interpolable, sin
medir nada en JavaScript ni fijar altura. El cierre necesita script: el clic en el <summary> se
intercepta, el elemento recibe is-closing y sigue abierto mientras la fila vuelve a 0fr, y solo al final cae el
open. Reabrir en mitad del cierre cancela la salida y sigue desde el punto en que estaba la altura.
El fin de la animación viene del transitionend, no de un número
Un tiempo en JavaScript tendría que reflejar el token del CSS, y los dos se desfasan: 220 ms contra 280 ms arrancaba el contenido antes del final, y el cierre se veía cortado. El elemento se cierra cuando la fila del grid termina su transición; un timeout de 500 ms queda solo como red de seguridad, para pestaña oculta o movimiento reducido, cuando el evento no llega.
El espacio inferior es margen, no padding
Una pista en fr no se encoge por debajo del mínimo del contenido, y el padding cuenta en ese mínimo: con padding en el contenido, el elemento cerrado dejaba una rendija de 14px. Por eso el espacio sale del margen del último hijo, que el overflow: hidden recorta hasta cero. Si estilizas el contenido, mantén la regla.
La primera apertura de un <details> suele atascarse, porque el navegador todavía no midió lo que estaba cerrado.
Al iniciar, el componente abre y cierra cada elemento en el mismo bloque síncrono — no llega a pintarse nada — solo para adelantar ese
cálculo. Con prefers-reduced-motion, la transición baja a 1 ms.
Teclado y accesibilidad
Todo aquí viene del <details>: el <summary> es enfocable, y el lector de pantalla anuncia
plegado y desplegado sin ningún aria-* nuestro.
| Tecla | Acción |
|---|---|
Tab | Va de título en título, y entra en el contenido de los elementos abiertos |
Enter Espacio | Abre o cierra el elemento enfocado, con la misma animación del clic |
La flecha es dibujo y lleva aria-hidden, para no leerse junto con el título. Un <details> cerrado no
tiene nada enfocable en el recorrido del Tab: el campo de un elemento plegado no se alcanza hasta que alguien lo abre.
API
Generada a partir del código en cada build — si algo no está aquí, no existe.
[data-tuc-accordion]new Tucano.Accordion(alvo, opcoes)data-singleopen close destroyOpciones
Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.
| Opción | Por defecto | Para qué |
|---|---|---|
single | false | abrir um recolhe os outros |