Tucano v0.37.2

Acordeão

Blocos que recolhem e expandem, feitos sobre <details> e <summary>. O nativo já resolve teclado, semântica e estado, e abre e fecha antes de o JavaScript carregar. O componente entra só onde o nativo não vai: animar.

Preciso de build no meu projeto?

Não. O CSS já vai compilado e o Tailwind é usado só dentro da biblioteca. Do seu lado são dois arquivos e nada mais.

Funciona com HTMX?

Sim. O que chega por swap é inicializado sozinho, então o acordeão vindo do servidor já nasce animado.

E se o JavaScript falhar?

Este acordeão continua abrindo e fechando: ele é <details> nativo. O que se perde é só a animação.

Exemplos

O mesmo <details>, com um atributo ou uma classe a mais.

Vários abertos

Sem data-single, cada item abre e fecha por conta própria.

Dados do contrato

Número 2026-0142, vigência de 12 meses.

Partes

Tucano Comércio Ltda e Padaria Pão Quente.

Cláusulas

Reajuste anual pelo IPCA e multa de 2% por atraso.

Um de cada vez

data-single="true" recolhe os outros ao abrir um.

Plano mensal

Cobrança todo dia 10, cancele quando quiser.

Plano anual

Dois meses de desconto, pago de uma vez.

Sob consulta

Para mais de 50 usuários.

Conteúdo de verdade dentro

A altura é medida pelo navegador, não chutada: formulário, tabela e imagem abrem igual.

Filtros do relatório
Colunas visíveis

Sem divisórias, para menu

is-plain tira as linhas e deixa o título com cara de rótulo de grupo.

Como usar

O HTML é o de sempre. Os <details> precisam ser filhos diretos do elemento marcado, e o open escrito no template diz quais nascem abertos.

<div class="tuc-accordion" data-tuc-accordion data-single="true">
  <details open>
    <summary>Projetos</summary>
    <p>Listar, criar e acompanhar vistorias.</p>
  </details>
  <details>
    <summary>Relatórios</summary>
    <p>Mensal, por equipe e por período.</p>
  </details>
</div>

O script acrescenta o que falta: a classe tuc-accordion no contêiner, tuc-accordion__item em cada <details>, tuc-accordion__trigger no <summary>, a seta, e embrulha o resto do conteúdo em tuc-accordion__body e tuc-accordion__content. Até lá, o CSS já desenha o acordeão cru com as mesmas linhas e o mesmo respiro, para nada pular quando o JavaScript chega.

No template do 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>

Em JavaScript

const faq = new Tucano.Accordion('#faq', { single: true });

const item = document.querySelector('#faq > details:nth-child(2)');
faq.open(item);      // com single, recolhe os outros
faq.close(item);     // anima e só então tira o open
faq.items;           // os <details>, na ordem
faq.destroy();

Como anima

A altura do conteúdo é auto, e auto não transiciona. Ao fechar, o navegador ainda some com o conteúdo no mesmo quadro em que o open cai.

A abertura é CSS puro: o corpo é um grid de uma linha, e a linha anima de 0fr a 1fr — interpolável, sem medir nada em JavaScript nem fixar altura. O fechamento precisa de script: o clique no <summary> é interceptado, o item ganha is-closing e continua aberto enquanto a linha volta a 0fr, e só no fim o open cai. Reabrir no meio do fechamento cancela a saída e segue do ponto em que a altura estava.

O fim da animação vem do transitionend, não de um número

Um tempo em JavaScript teria de espelhar o token do CSS, e os dois divergem: 220 ms contra 280 ms arrancava o conteúdo antes do fim, e o fechamento aparecia cortado. O item fecha quando a linha do grid termina de transicionar; um timeout de 500 ms fica só como rede de segurança, para aba oculta ou movimento reduzido, quando o evento não chega.

O respiro de baixo é margem, não padding

Uma faixa em fr não encolhe abaixo do mínimo do conteúdo, e o padding entra nesse mínimo: com padding no conteúdo, o item fechado ficava com uma fresta de 14px. Por isso o espaço sai da margem do último filho, que o overflow: hidden recorta até zero. Se estilizar o conteúdo, mantenha a regra.

A primeira abertura de um <details> costuma engasgar, porque o navegador ainda não mediu o que estava fechado. Ao iniciar, o componente abre e fecha cada item no mesmo bloco síncrono — nada chega a pintar — só para adiantar esse cálculo. Com prefers-reduced-motion, a transição cai para 1 ms.

Teclado e acessibilidade

Tudo aqui vem do <details>: o <summary> é focável, e o leitor de tela anuncia recolhido e expandido sem nenhum aria-* nosso.

TeclaAção
TabAnda de título em título, e para dentro do conteúdo dos itens abertos
Enter EspaçoAbre ou fecha o item focado, com a mesma animação do clique

A seta é desenho e leva aria-hidden, para não ser lida junto do título. Um <details> fechado não tem nada focável no caminho do Tab: o campo de um item recolhido não é alcançado até alguém abrir o item.

API

Gerada do código a cada build — se algo não está aqui, não existe.

Marcação
[data-tuc-accordion]
Em JS
new Tucano.Accordion(alvo, opcoes)
Atributos
data-single
Métodos
open close destroy

Opções

OpçãoPadrãoPara quê
singlefalseabrir um recolhe os outros