Toggle Group
templates/components/ToggleGroup
É um grupo de botões pressionáveis (on/off) que exprime uma SELEÇÃO entre opções: em type=single vira um controle segmentado (troca de visão, alinhamento) e em type=multiple vira barra de formatação/filtros (negrito+itálico, tags). O estado é um CONJUNTO de itens ativos — e é justamente esse conjunto que o usuário quer restringir, submeter, lembrar e rotular, sem tocar no visual.
Base congelada
Capacidades 8
Conteúdo
Seleção mínima obrigatória
Impede o estado vazio: quando ligado,…
Dica por item (tooltip)
Mostra o aria-label do item como…
Lembrar seleção
Persiste os itens ativos (localStorage) e…
Rótulo do grupo
Adiciona um rótulo acessível e visível…
Comportamento
Limite de itens ativos
Em type=multiple, define o máximo de…
Contador de selecionados
Texto vivo com a quantidade de…
Atalho de teclado por item
Vincula uma tecla a cada item…
Sincronizar com formulário
Emite input(s) hidden com o nome…
Claude Code
Cole no Claude Code — ele acerta de primeira.
ToggleGroup (UI) — Design System (Symfony UX Toolkit / shadcn)
BASE CONGELADA (não mude por instância): Ficam congelados: variant (default/outline), size (sm/default/lg) e seu raio/altura, os cantos (rounded-lg, canto colado quando spacing=0), a fonte (text-sm font-medium), o gap por tokens (--spacing), o layout horizontal/vertical, o anel de foco (focus-visible ring-ring), os estados hover/disabled/aria-invalid e toda a a11y (role=group, aria-pressed, data-state=on/off, foco por teclado). A semântica single vs multiple e a estrutura ToggleGroup + ToggleGroup:Item também são base. Nada disso muda ao ligar/desligar uma capacidade.
USO: <twig:ToggleGroup variant="default" />
CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Seleção mínima obrigatória: Impede o estado vazio: quando ligado, o item ativo não pode ser desmarcado (exige sempre 1 pressionado). Essencial em type=single usado como troca de visão/alinhamento. [toggle]- Limite de itens ativos: Em type=multiple, define o máximo de itens simultaneamente pressionados (ex.: 3); ao atingir o teto, novos cliques são bloqueados até desmarcar outro. Puro comportamento. [number]- Contador de selecionados: Texto vivo com a quantidade de itens ativos, atualizado a cada toggle. Útil em multiple (filtros/tags). Conteúdo aditivo, sem alterar os itens. [select → N / N de M / N selecionados]- Dica por item (tooltip): Mostra o aria-label do item como tooltip ao passar o mouse ou focar — indispensável para toggles só de ícone (negrito/itálico). Camada aditiva sobre o botão. [select → Acima / Abaixo / Esquerda / Direita]- Atalho de teclado por item: Vincula uma tecla a cada item (ex.: Ctrl+B alterna o negrito), replicando o comportamento de uma barra de formatação real. Comportamental, sem tocar no visual. [toggle]- Sincronizar com formulário: Emite input(s) hidden com o nome informado carregando o(s) valor(es) ativo(s), fazendo o grupo submeter como um campo de formulário comum (single=1 valor, multiple=vários). [text]- Lembrar seleção: Persiste os itens ativos (localStorage) e os restaura no próximo carregamento — ideal para preferências como modo de visualização. Só comportamento e dados. [toggle]- Rótulo do grupo: Adiciona um rótulo acessível e visível associado ao grupo (aria-labelledby), dando nome à seleção (ex.: 'Formatação:'). Conteúdo aditivo posicionável, não altera os itens. [text]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): Ficam congelados: variant (default/outline), size (sm/default/lg) e seu raio/altura, os cantos (rounded-lg, canto colado quando spacing=0), a fonte (text-sm font-medium), o gap por tokens (--spacing), o layout horizontal/vertical, o anel de foco (focus-visible ring-ring), os estados hover/disabled/aria-invalid e toda a a11y (role=group, aria-pressed, data-state=on/off, foco por teclado). A semântica single vs multiple e a estrutura ToggleGroup + ToggleGroup:Item também são base
LLM / MCP
Via MCP: tool get_component com {"id": "toggle-group"} · list_capabilities("toggle-group").
Spec crua: config/ds-specs/toggle-group.json · Conectar o MCP.
Spec machine-readable (JSON)
{
"$schema_version": "1.0",
"id": "toggle-group",
"component": "ToggleGroup",
"eixo": "ui",
"particularidade": "É um grupo de botões pressionáveis (on/off) que exprime uma SELEÇÃO entre opções: em type=single vira um controle segmentado (troca de visão, alinhamento) e em type=multiple vira barra de formatação/filtros (negrito+itálico, tags). O estado é um CONJUNTO de itens ativos — e é justamente esse conjunto que o usuário quer restringir, submeter, lembrar e rotular, sem tocar no visual.",
"base_congelada": "Ficam congelados: variant (default/outline), size (sm/default/lg) e seu raio/altura, os cantos (rounded-lg, canto colado quando spacing=0), a fonte (text-sm font-medium), o gap por tokens (--spacing), o layout horizontal/vertical, o anel de foco (focus-visible ring-ring), os estados hover/disabled/aria-invalid e toda a a11y (role=group, aria-pressed, data-state=on/off, foco por teclado). A semântica single vs multiple e a estrutura ToggleGroup + ToggleGroup:Item também são base. Nada disso muda ao ligar/desligar uma capacidade.",
"props": [
{
"name": "variant",
"type": "'default'|'outline'",
"default": "default",
"description": "The visual style variant. Defaults to `default` #}"
},
{
"name": "size",
"type": "'default'|'sm'|'lg'",
"default": "default",
"description": "The toggle group size. Defaults to `default` #}"
},
{
"name": "type",
"type": "'single'|'multiple'",
"default": "multiple",
"description": "Whether only one or multiple items can be active. Defaults to `multiple` #}"
},
{
"name": "spacing",
"type": "number",
"default": "2",
"description": "Gap between toggle group items. Defaults to `2` #}"
},
{
"name": "orientation",
"type": "'horizontal'|'vertical'",
"default": "horizontal",
"description": "The layout direction. Defaults to `horizontal` #}"
},
{
"name": "disabled",
"type": "boolean",
"default": "false",
"description": "Whether all items in the group are disabled. Defaults to `false` #}"
}
],
"capacidades": [
{
"id": "sele-o-m-nima-obrigat-ria",
"nome": "Seleção mínima obrigatória",
"descricao": "Impede o estado vazio: quando ligado, o item ativo não pode ser desmarcado (exige sempre 1 pressionado). Essencial em type=single usado como troca de visão/alinhamento.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "limite-de-itens-ativos",
"nome": "Limite de itens ativos",
"descricao": "Em type=multiple, define o máximo de itens simultaneamente pressionados (ex.: 3); ao atingir o teto, novos cliques são bloqueados até desmarcar outro. Puro comportamento.",
"controle": "number",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (number). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "contador-de-selecionados",
"nome": "Contador de selecionados",
"descricao": "Texto vivo com a quantidade de itens ativos, atualizado a cada toggle. Útil em multiple (filtros/tags). Conteúdo aditivo, sem alterar os itens.",
"controle": "select",
"opcoes": [
"N",
"N de M",
"N selecionados"
],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "dica-por-item-tooltip",
"nome": "Dica por item (tooltip)",
"descricao": "Mostra o aria-label do item como tooltip ao passar o mouse ou focar — indispensável para toggles só de ícone (negrito/itálico). Camada aditiva sobre o botão.",
"controle": "select",
"opcoes": [
"Acima",
"Abaixo",
"Esquerda",
"Direita"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "atalho-de-teclado-por-item",
"nome": "Atalho de teclado por item",
"descricao": "Vincula uma tecla a cada item (ex.: Ctrl+B alterna o negrito), replicando o comportamento de uma barra de formatação real. Comportamental, sem tocar no visual.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "sincronizar-com-formul-rio",
"nome": "Sincronizar com formulário",
"descricao": "Emite input(s) hidden com o nome informado carregando o(s) valor(es) ativo(s), fazendo o grupo submeter como um campo de formulário comum (single=1 valor, multiple=vários).",
"controle": "text",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "lembrar-sele-o",
"nome": "Lembrar seleção",
"descricao": "Persiste os itens ativos (localStorage) e os restaura no próximo carregamento — ideal para preferências como modo de visualização. Só comportamento e dados.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "r-tulo-do-grupo",
"nome": "Rótulo do grupo",
"descricao": "Adiciona um rótulo acessível e visível associado ao grupo (aria-labelledby), dando nome à seleção (ex.: 'Formatação:'). Conteúdo aditivo posicionável, não altera os itens.",
"controle": "text",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle-group; comportamento via controller Stimulus. Não altera a base."
}
],
"snippet_uso": "<twig:ToggleGroup variant=\"default\" />",
"exemplo_demo": "<twig:ToggleGroup variant=\"outline\" type=\"multiple\">\n <twig:ToggleGroup:Item aria-label=\"Toggle bold\">\n <twig:ux:icon name=\"lucide:bold\" />\n </twig:ToggleGroup:Item>\n <twig:ToggleGroup:Item aria-label=\"Toggle italic\">\n <twig:ux:icon name=\"lucide:italic\" />\n </twig:ToggleGroup:Item>\n <twig:ToggleGroup:Item aria-label=\"Toggle strikethrough\">\n <twig:ux:icon name=\"lucide:underline\" />\n </twig:ToggleGroup:Item>\n</twig:ToggleGroup>",
"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": "Ficam congelados: variant (default/outline), size (sm/default/lg) e seu raio/altura, os cantos (rounded-lg, canto colado quando spacing=0), a fonte (text-sm font-medium), o gap por tokens (--spacing), o layout horizontal/vertical, o anel de foco (focus-visible ring-ring), os estados hover/disabled/aria-invalid e toda a a11y (role=group, aria-pressed, data-state=on/off, foco por teclado). A semântica single vs multiple e a estrutura ToggleGroup + ToggleGroup:Item também são base",
"mcp": {
"tool": "get_component",
"args": {
"id": "toggle-group"
}
}
}