Collapsible
templates/components/Collapsible
É uma ÚNICA região de disclosure: exatamente um gatilho que controla um único bloco de conteúdo, revelando/ocultando sob demanda. Diferente do Accordion, não tem conjunto de itens nem lógica de único-vs-múltiplo — sua identidade inteira é a transição mostrar/ocultar e o vínculo de a11y entre gatilho e conteúdo.
Base congelada
Order #4189
Capacidades 8
Conteúdo
Indicador de estado com rotação
Vincula um chevron ao data-state do…
Conteúdo sob demanda (lazy)
Só renderiza/carrega o conteúdo interno na…
Estado inicial responsivo
Decide o aberto/fechado inicial conforme o…
Rótulo dinâmico do gatilho
Troca o texto (e o sr-only)…
Prévia quando fechado
Mostra um resumo aditivo do conteúdo…
Comportamento
Animação de transição
Como o conteúdo entra/sai ao alternar:…
Lembrar estado (persistência)
Guarda aberto/fechado por id no storage…
Área de clique do gatilho
Escolhe o que dispara o toggle…
Claude Code
Cole no Claude Code — ele acerta de primeira.
Collapsible (UX) — Design System (Symfony UX Toolkit / shadcn)
BASE CONGELADA (não mude por instância): Congelado: a estrutura div[data-controller=collapsible] > Collapsible:Trigger + Collapsible:Content; o mecanismo de mostrar/ocultar via atributo `hidden`; o contrato de a11y (aria-expanded no gatilho, aria-hidden no conteúdo, data-state open/closed espelhado nos dois); o prop `open` (default false) e o controller Stimulus `collapsible`; e todos os tokens visuais (fonte, cor, cantos, espaçamento) herdados dos componentes compostos (Button, bordas). Nada disso muda por instância — enrichers só adicionam comportamento/conteúdo aditivo em volta desse contrato.
USO: <twig:Collapsible />
CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Animação de transição: Como o conteúdo entra/sai ao alternar: instantâneo (hidden) ou animando altura/opacidade, sempre respeitando prefers-reduced-motion. [select → Instantâneo / Deslizar (altura) / Esmaecer / Deslizar + esmaecer]- Indicador de estado com rotação: Vincula um chevron ao data-state do gatilho, girando-o ao abrir/fechar como sinal do disclosure; sem alterar cor/tamanho da base. [toggle]- Lembrar estado (persistência): Guarda aberto/fechado por id no storage do navegador e restaura o mesmo estado na próxima visita. [toggle]- Conteúdo sob demanda (lazy): Só renderiza/carrega o conteúdo interno na primeira abertura, evitando custo quando permanece fechado. [toggle]- Estado inicial responsivo: Decide o aberto/fechado inicial conforme o viewport, sem tocar no prop nem no visual da base. [select → Sempre fechado / Sempre aberto / Aberto no desktop / fechado no mobile / Aberto no mobile / fechado no desktop]- Rótulo dinâmico do gatilho: Troca o texto (e o sr-only) do gatilho conforme o estado, ex.: 'Mostrar detalhes' quando fechado ↔ 'Ocultar detalhes' quando aberto. [text]- Área de clique do gatilho: Escolhe o que dispara o toggle sem duplicar o componente: apenas o botão/ícone ou toda a linha do cabeçalho. [select → Só o botão/ícone / Toda a linha do cabeçalho]- Prévia quando fechado: Mostra um resumo aditivo do conteúdo oculto no cabeçalho enquanto fechado (ex.: contagem '3 itens' ou primeira linha), somindo ao abrir. [toggle]FAÇA:
- Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.
- Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.
- Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}.
NÃO FAÇA:
- Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.
- Não crie variante/fork para a mesma coisa — existe UMA base por componente.
- Não reimplemente o componente nem adicione toolchain Node.
TESTE ANTES DE MUDAR: "é função ou é base?" — função = capacidade opt-in; base = mude o token na fonte única (assets/styles/app.css), para todos.
A11Y (herdada da base): o contrato de a11y (aria-expanded no gatilho, aria-hidden no conteúdo, data-state open/closed espelhado nos dois)
LLM / MCP
Via MCP: tool get_component com {"id": "collapsible"} · list_capabilities("collapsible").
Spec crua: config/ds-specs/collapsible.json · Conectar o MCP.
Spec machine-readable (JSON)
{
"$schema_version": "1.0",
"id": "collapsible",
"component": "Collapsible",
"eixo": "ux",
"particularidade": "É uma ÚNICA região de disclosure: exatamente um gatilho que controla um único bloco de conteúdo, revelando/ocultando sob demanda. Diferente do Accordion, não tem conjunto de itens nem lógica de único-vs-múltiplo — sua identidade inteira é a transição mostrar/ocultar e o vínculo de a11y entre gatilho e conteúdo.",
"base_congelada": "Congelado: a estrutura div[data-controller=collapsible] > Collapsible:Trigger + Collapsible:Content; o mecanismo de mostrar/ocultar via atributo `hidden`; o contrato de a11y (aria-expanded no gatilho, aria-hidden no conteúdo, data-state open/closed espelhado nos dois); o prop `open` (default false) e o controller Stimulus `collapsible`; e todos os tokens visuais (fonte, cor, cantos, espaçamento) herdados dos componentes compostos (Button, bordas). Nada disso muda por instância — enrichers só adicionam comportamento/conteúdo aditivo em volta desse contrato.",
"props": [
{
"name": "open",
"type": "boolean",
"default": "false",
"description": "Whether the collapsible is open by default. Defaults to `false` #}"
}
],
"capacidades": [
{
"id": "anima-o-de-transi-o",
"nome": "Animação de transição",
"descricao": "Como o conteúdo entra/sai ao alternar: instantâneo (hidden) ou animando altura/opacidade, sempre respeitando prefers-reduced-motion.",
"controle": "select",
"opcoes": [
"Instantâneo",
"Deslizar (altura)",
"Esmaecer",
"Deslizar + esmaecer"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "indicador-de-estado-com-rota-o",
"nome": "Indicador de estado com rotação",
"descricao": "Vincula um chevron ao data-state do gatilho, girando-o ao abrir/fechar como sinal do disclosure; sem alterar cor/tamanho da base.",
"controle": "toggle",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "lembrar-estado-persist-ncia",
"nome": "Lembrar estado (persistência)",
"descricao": "Guarda aberto/fechado por id no storage do navegador e restaura o mesmo estado na próxima visita.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "conte-do-sob-demanda-lazy",
"nome": "Conteúdo sob demanda (lazy)",
"descricao": "Só renderiza/carrega o conteúdo interno na primeira abertura, evitando custo quando permanece fechado.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "estado-inicial-responsivo",
"nome": "Estado inicial responsivo",
"descricao": "Decide o aberto/fechado inicial conforme o viewport, sem tocar no prop nem no visual da base.",
"controle": "select",
"opcoes": [
"Sempre fechado",
"Sempre aberto",
"Aberto no desktop / fechado no mobile",
"Aberto no mobile / fechado no desktop"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "r-tulo-din-mico-do-gatilho",
"nome": "Rótulo dinâmico do gatilho",
"descricao": "Troca o texto (e o sr-only) do gatilho conforme o estado, ex.: 'Mostrar detalhes' quando fechado ↔ 'Ocultar detalhes' quando aberto.",
"controle": "text",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "rea-de-clique-do-gatilho",
"nome": "Área de clique do gatilho",
"descricao": "Escolhe o que dispara o toggle sem duplicar o componente: apenas o botão/ícone ou toda a linha do cabeçalho.",
"controle": "select",
"opcoes": [
"Só o botão/ícone",
"Toda a linha do cabeçalho"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "pr-via-quando-fechado",
"nome": "Prévia quando fechado",
"descricao": "Mostra um resumo aditivo do conteúdo oculto no cabeçalho enquanto fechado (ex.: contagem '3 itens' ou primeira linha), somindo ao abrir.",
"controle": "toggle",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ux/collapsible; comportamento via controller Stimulus. Não altera a base."
}
],
"snippet_uso": "<twig:Collapsible />",
"exemplo_demo": "<twig:Collapsible class=\"flex w-[350px] flex-col gap-2 self-start\">\n <div class=\"flex items-center justify-between gap-4 px-4\">\n <h4 class=\"text-sm font-semibold\">Order #4189</h4>\n <twig:Collapsible:Trigger>\n <twig:Button variant=\"ghost\" size=\"icon\" class=\"size-8\" {{ ...collapsible_trigger_attrs }}>\n <twig:ux:icon name=\"lucide:chevrons-up-down\" class=\"size-4\" />\n <span class=\"sr-only\">Toggle details</span>\n </twig:Button>\n </twig:Collapsible:Trigger>\n </div>\n <div class=\"flex items-center justify-between rounded-md border px-4 py-2 text-sm\">\n <span class=\"text-muted-foreground\">Status</span>\n <span class=\"font-medium\">Shipped</span>\n </div>\n <twig:Collapsible:Content class=\"flex flex-col gap-2\">\n <div class=\"rounded-md border px-4 py-2 text-sm\">\n <p class=\"font-medium\">Shipping address</p>\n <p class=\"text-muted-foreground\">100 Market St, San Francisco</p>\n </div>\n <div class=\"rounded-md border px-4 py-2 text-sm\">\n <p class=\"font-medium\">Items</p>\n <p class=\"text-muted-foreground\">2x Studio Headphones</p>\n </div>\n </twig:Collapsible:Content>\n</twig:Collapsible>",
"regras": {
"faca": [
"Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.",
"Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.",
"Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}."
],
"nao_faca": [
"Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.",
"Não crie variante/fork para a mesma coisa — existe UMA base por componente.",
"Não reimplemente o componente nem adicione toolchain Node."
]
},
"a11y": "o contrato de a11y (aria-expanded no gatilho, aria-hidden no conteúdo, data-state open/closed espelhado nos dois)",
"mcp": {
"tool": "get_component",
"args": {
"id": "collapsible"
}
}
}