Pular para o conteúdo

Toggle

templates/components/Toggle

O Toggle é um botão binário de dois estados (pressed on/off) que RETÉM e REFLETE um estado — diferente do Button (que dispara uma ação e volta) e do Switch/Checkbox (campo de formulário). É frequentemente só-ícone, em toolbars (formatação, fixar, mudo, favoritar). Por isso seus enriquecedores giram em torno de refletir, persistir, submeter e anunciar esse estado binário — nunca de mudar a aparência.

Base congelada

Conteúdo

Rótulo por estado

Troca o texto do rótulo conforme…

Troca o texto do rótulo conforme ligado/desligado, refletindo a ação inversa (ex.: Seguir ↔ Seguindo, Silenciar ↔ Com som, Mostrar ↔ Ocultar). Mesma fonte/tamanho da base.

Ícone por estado

Alterna o glifo do ícone entre…

Alterna o glifo do ícone entre os dois estados (ex.: volume ↔ mudo, olho ↔ olho-fechado, sino ↔ sino-cortado, coração vazio ↔ cheio). Tamanho, cor e slot do ícone continuam os da base.

Comportamento

Persistir estado

Lembra o pressed entre recarregamentos e…

Lembra o pressed entre recarregamentos e navegações (localStorage por id) — ideal p/ toggles de preferência como modo compacto, densidade ou tema. Só comportamento, sem eco visual.

Enviar em formulário

Espelha o aria-pressed num <input hidden>…

Espelha o aria-pressed num <input hidden> com o name informado, permitindo que o toggle submeta um booleano no form (a base é um button e não envia nada). Elemento invisível, não toca no visual.

Atalho de teclado

Vincula uma tecla/combinação que alterna o…

Vincula uma tecla/combinação que alterna o toggle sem clique (ex.: Mod+B, Mod+I num toolbar de formatação; M para mudo). Puro comportamento.

Dica (tooltip) por estado

Mostra dica no hover/focus — essencial…

Mostra dica no hover/focus — essencial em toggle só-ícone p/ discoverability e a11y; o texto pode diferir por estado (Fixar ↔ Desafixar). Escolhe-se o lado onde aparece. Usa o componente Tooltip, sobreposto, sem alterar o botão. posicionável

Alternância assíncrona

Para toggles ligados ao servidor (favoritar,…

Para toggles ligados ao servidor (favoritar, publicar, mudo, ativar): marca pendência no clique e REVERTE o estado se a requisição falhar (otimista + rollback). Reaproveita o disabled/opacity e o slot de ícone já existentes na base.

Emitir evento ao alternar

Dispara um evento/callback com o novo…

Dispara um evento/callback com o novo estado para outras partes da página reagirem (ex.: toggle 'modo compacto' → recompõe a lista; 'mudo' → pausa o player). Integração comportamental, sem UI.
Código gerado

Cole no Claude Code — ele acerta de primeira.

Toggle (UI) — Design System (Symfony UX Toolkit / shadcn)

BASE CONGELADA (não mude por instância): Elemento <button type="button"> com aria-pressed + data-state=on/off dirigido pelo controller Stimulus `toggle`. Congelados: cantos rounded-lg (sm: rounded-md); alturas h-8/h-7/h-9 e min-w correspondentes (default/sm/lg); padding px-2.5 (com ajuste automático quando há ícone inline-start/inline-end); tipografia text-sm font-medium whitespace-nowrap; gap-1; hover:bg-muted/hover:text-foreground; foco visível (border-ring + ring-[3px] ring-ring/50); estado LIGADO = bg-muted (aria-pressed=true / data-state=on); disabled opacity-50 pointer-events-none; erro via aria-invalid; ícone shrink-0 size-4 (sm: size-3.5) nos slots inline-start/inline-end; variants default|outline e sizes default|sm|lg; toda a semântica/a11y (aria-pressed, aria-invalid, foco) e locale. Ligar/desligar qualquer capacidade NÃO altera nada disso.

USO: <twig:Toggle variant="default" />

CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Rótulo por estado: Troca o texto do rótulo conforme ligado/desligado, refletindo a ação inversa (ex.: Seguir ↔ Seguindo, Silenciar ↔ Com som, Mostrar ↔ Ocultar). Mesma fonte/tamanho da base. [text]- Ícone por estado: Alterna o glifo do ícone entre os dois estados (ex.: volume ↔ mudo, olho ↔ olho-fechado, sino ↔ sino-cortado, coração vazio ↔ cheio). Tamanho, cor e slot do ícone continuam os da base. [text]- Persistir estado: Lembra o pressed entre recarregamentos e navegações (localStorage por id) — ideal p/ toggles de preferência como modo compacto, densidade ou tema. Só comportamento, sem eco visual. [toggle]- Enviar em formulário: Espelha o aria-pressed num &lt;input hidden&gt; com o name informado, permitindo que o toggle submeta um booleano no form (a base é um button e não envia nada). Elemento invisível, não toca no visual. [text]- Atalho de teclado: Vincula uma tecla/combinação que alterna o toggle sem clique (ex.: Mod+B, Mod+I num toolbar de formatação; M para mudo). Puro comportamento. [text]- Dica (tooltip) por estado: Mostra dica no hover/focus — essencial em toggle só-ícone p/ discoverability e a11y; o texto pode diferir por estado (Fixar ↔ Desafixar). Escolhe-se o lado onde aparece. Usa o componente Tooltip, sobreposto, sem alterar o botão. [select → Topo / Base / Esquerda / Direita]- Alternância assíncrona: Para toggles ligados ao servidor (favoritar, publicar, mudo, ativar): marca pendência no clique e REVERTE o estado se a requisição falhar (otimista + rollback). Reaproveita o disabled/opacity e o slot de ícone já existentes na base. [toggle]- Emitir evento ao alternar: Dispara um evento/callback com o novo estado para outras partes da página reagirem (ex.: toggle 'modo compacto' → recompõe a lista; 'mudo' → pausa o player). Integração comportamental, sem UI. [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): com aria-pressed + data-state=on/off dirigido pelo controller Stimulus `toggle`. foco visível (border-ring + ring-[3px] ring-ring/50). estado LIGADO = bg-muted (aria-pressed=true / data-state=on)

Via MCP: tool get_component com {"id": "toggle"} · list_capabilities("toggle").

Spec crua: config/ds-specs/toggle.json · Conectar o MCP.

Spec machine-readable (JSON)

{
    "$schema_version": "1.0",
    "id": "toggle",
    "component": "Toggle",
    "eixo": "ui",
    "particularidade": "O Toggle é um botão binário de dois estados (pressed on/off) que RETÉM e REFLETE um estado — diferente do Button (que dispara uma ação e volta) e do Switch/Checkbox (campo de formulário). É frequentemente só-ícone, em toolbars (formatação, fixar, mudo, favoritar). Por isso seus enriquecedores giram em torno de refletir, persistir, submeter e anunciar esse estado binário — nunca de mudar a aparência.",
    "base_congelada": "Elemento &lt;button type=\"button\"&gt; com aria-pressed + data-state=on/off dirigido pelo controller Stimulus `toggle`. Congelados: cantos rounded-lg (sm: rounded-md); alturas h-8/h-7/h-9 e min-w correspondentes (default/sm/lg); padding px-2.5 (com ajuste automático quando há ícone inline-start/inline-end); tipografia text-sm font-medium whitespace-nowrap; gap-1; hover:bg-muted/hover:text-foreground; foco visível (border-ring + ring-[3px] ring-ring/50); estado LIGADO = bg-muted (aria-pressed=true / data-state=on); disabled opacity-50 pointer-events-none; erro via aria-invalid; ícone shrink-0 size-4 (sm: size-3.5) nos slots inline-start/inline-end; variants default|outline e sizes default|sm|lg; toda a semântica/a11y (aria-pressed, aria-invalid, foco) e locale. Ligar/desligar qualquer capacidade NÃO altera nada disso.",
    "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 size. Defaults to `default` #}"
        },
        {
            "name": "pressed",
            "type": "boolean",
            "default": "false",
            "description": "Whether the toggle is initially pressed. Defaults to `false` #}"
        }
    ],
    "capacidades": [
        {
            "id": "r-tulo-por-estado",
            "nome": "Rótulo por estado",
            "descricao": "Troca o texto do rótulo conforme ligado/desligado, refletindo a ação inversa (ex.: Seguir ↔ Seguindo, Silenciar ↔ Com som, Mostrar ↔ Ocultar). Mesma fonte/tamanho da base.",
            "controle": "text",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "cone-por-estado",
            "nome": "Ícone por estado",
            "descricao": "Alterna o glifo do ícone entre os dois estados (ex.: volume ↔ mudo, olho ↔ olho-fechado, sino ↔ sino-cortado, coração vazio ↔ cheio). Tamanho, cor e slot do ícone continuam os da base.",
            "controle": "text",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "persistir-estado",
            "nome": "Persistir estado",
            "descricao": "Lembra o pressed entre recarregamentos e navegações (localStorage por id) — ideal p/ toggles de preferência como modo compacto, densidade ou tema. Só comportamento, sem eco visual.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "enviar-em-formul-rio",
            "nome": "Enviar em formulário",
            "descricao": "Espelha o aria-pressed num &lt;input hidden&gt; com o name informado, permitindo que o toggle submeta um booleano no form (a base é um button e não envia nada). Elemento invisível, não toca no visual.",
            "controle": "text",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "atalho-de-teclado",
            "nome": "Atalho de teclado",
            "descricao": "Vincula uma tecla/combinação que alterna o toggle sem clique (ex.: Mod+B, Mod+I num toolbar de formatação; M para mudo). Puro comportamento.",
            "controle": "text",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "dica-tooltip-por-estado",
            "nome": "Dica (tooltip) por estado",
            "descricao": "Mostra dica no hover/focus — essencial em toggle só-ícone p/ discoverability e a11y; o texto pode diferir por estado (Fixar ↔ Desafixar). Escolhe-se o lado onde aparece. Usa o componente Tooltip, sobreposto, sem alterar o botão.",
            "controle": "select",
            "opcoes": [
                "Topo",
                "Base",
                "Esquerda",
                "Direita"
            ],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "altern-ncia-ass-ncrona",
            "nome": "Alternância assíncrona",
            "descricao": "Para toggles ligados ao servidor (favoritar, publicar, mudo, ativar): marca pendência no clique e REVERTE o estado se a requisição falhar (otimista + rollback). Reaproveita o disabled/opacity e o slot de ícone já existentes na base.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "emitir-evento-ao-alternar",
            "nome": "Emitir evento ao alternar",
            "descricao": "Dispara um evento/callback com o novo estado para outras partes da página reagirem (ex.: toggle 'modo compacto' → recompõe a lista; 'mudo' → pausa o player). Integração comportamental, sem UI.",
            "controle": "text",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/toggle; comportamento via controller Stimulus. Não altera a base."
        }
    ],
    "snippet_uso": "<twig:Toggle variant=\"default\" />",
    "exemplo_demo": "<twig:Toggle variant=\"outline\" size=\"sm\" aria-label=\"Toggle bookmark\">\n    <twig:ux:icon name=\"lucide:bookmark\" class=\"group-data-[state=on]/toggle:fill-current\" />\n    Bookmark\n</twig:Toggle>",
    "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": "com aria-pressed + data-state=on/off dirigido pelo controller Stimulus `toggle`. foco visível (border-ring + ring-[3px] ring-ring/50). estado LIGADO = bg-muted (aria-pressed=true / data-state=on)",
    "mcp": {
        "tool": "get_component",
        "args": {
            "id": "toggle"
        }
    }
}