Pular para o conteúdo

Spinner

templates/components/Spinner

É o único indicador puramente visual, indeterminado e animado de "ocupado" — não tem conteúdo próprio nem valor determinado (ao contrário do Progress). Por isso todo enriquecimento vive no CICLO DE VIDA do carregamento assíncrono e no CONTEXTO ao redor do ícone, nunca na aparência do giro.

Base congelada

Processing payment...
$100.00

Conteúdo

Atraso de exibição

Só renderiza o spinner após X…

Só renderiza o spinner após X ms, evitando o 'flash' de spinner em cargas rápidas.

Rótulo visível

Mostra o texto do aria-label também…

Mostra o texto do aria-label também visualmente ao lado do ícone (ex.: 'Carregando…'), usando a tipografia da base. posicionável

Mensagens progressivas

Alterna uma sequência de mensagens de…

Alterna uma sequência de mensagens de etapa ao longo do tempo em operações longas, dando contexto do que está acontecendo.

Tempo decorrido

Exibe um contador ao vivo (ex.:…

Exibe um contador ao vivo (ex.: '12s') ao lado do spinner para sinalizar que a operação segue viva. posicionável

Overlay bloqueante

Centraliza o spinner sobre a região-alvo…

Centraliza o spinner sobre a região-alvo com um scrim que bloqueia a interação enquanto carrega. posicionável

Botão cancelar

Adiciona uma ação de cancelar ao…

Adiciona uma ação de cancelar ao lado do spinner para abortar operações canceláveis. posicionável

Comportamento

Timeout com aviso

Após N segundos sem concluir, revela…

Após N segundos sem concluir, revela uma mensagem de 'está demorando mais que o esperado / tentar novamente'.

Acessibilidade

Anúncio de conclusão

Ao resolver o carregamento, anuncia uma…

Ao resolver o carregamento, anuncia uma mensagem de 'Concluído' para leitores de tela, complementando o role=status congelado.
Código gerado

Cole no Claude Code — ele acerta de primeira.

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

BASE CONGELADA (não mude por instância): Ícone lucide:loader-2 (a forma do indicador), animate-spin (giro e sua velocidade), size-4 (tamanho fixo), cor herdada via currentColor (sem cor própria), cantos/raio e ausência de espaçamento/moldura próprios, data-slot="spinner", e a semântica de a11y congelada: role="status" + aria-label (default 'Loading'). Nada disso muda ao ligar/desligar qualquer capacidade.

USO: <twig:Spinner />

CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Atraso de exibição: Só renderiza o spinner após X ms, evitando o 'flash' de spinner em cargas rápidas. [number]- Rótulo visível: Mostra o texto do aria-label também visualmente ao lado do ícone (ex.: 'Carregando…'), usando a tipografia da base. [text]- Mensagens progressivas: Alterna uma sequência de mensagens de etapa ao longo do tempo em operações longas, dando contexto do que está acontecendo. [multi → Conectando… / Processando… / Baixando… / Quase lá… / Finalizando…]- Tempo decorrido: Exibe um contador ao vivo (ex.: '12s') ao lado do spinner para sinalizar que a operação segue viva. [toggle]- Timeout com aviso: Após N segundos sem concluir, revela uma mensagem de 'está demorando mais que o esperado / tentar novamente'. [number]- Overlay bloqueante: Centraliza o spinner sobre a região-alvo com um scrim que bloqueia a interação enquanto carrega. [toggle]- Botão cancelar: Adiciona uma ação de cancelar ao lado do spinner para abortar operações canceláveis. [toggle]- Anúncio de conclusão: Ao resolver o carregamento, anuncia uma mensagem de 'Concluído' para leitores de tela, complementando o role=status congelado. [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): Ícone lucide:loader-2 (a forma do indicador), animate-spin (giro e sua velocidade), size-4 (tamanho fixo), cor herdada via currentColor (sem cor própria), cantos/raio e ausência de espaçamento/moldura próprios, data-slot="spinner", e a semântica de a11y congelada: role="status" + aria-label (default 'Loading')

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

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

Spec machine-readable (JSON)

{
    "$schema_version": "1.0",
    "id": "spinner",
    "component": "Spinner",
    "eixo": "ui",
    "particularidade": "É o único indicador puramente visual, indeterminado e animado de \"ocupado\" — não tem conteúdo próprio nem valor determinado (ao contrário do Progress). Por isso todo enriquecimento vive no CICLO DE VIDA do carregamento assíncrono e no CONTEXTO ao redor do ícone, nunca na aparência do giro.",
    "base_congelada": "Ícone lucide:loader-2 (a forma do indicador), animate-spin (giro e sua velocidade), size-4 (tamanho fixo), cor herdada via currentColor (sem cor própria), cantos/raio e ausência de espaçamento/moldura próprios, data-slot=\"spinner\", e a semântica de a11y congelada: role=\"status\" + aria-label (default 'Loading'). Nada disso muda ao ligar/desligar qualquer capacidade.",
    "props": [
        {
            "name": "label",
            "type": "",
            "default": "Loading",
            "description": ""
        }
    ],
    "capacidades": [
        {
            "id": "atraso-de-exibi-o",
            "nome": "Atraso de exibição",
            "descricao": "Só renderiza o spinner após X ms, evitando o 'flash' de spinner em cargas rápidas.",
            "controle": "number",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (number). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "r-tulo-vis-vel",
            "nome": "Rótulo visível",
            "descricao": "Mostra o texto do aria-label também visualmente ao lado do ícone (ex.: 'Carregando…'), usando a tipografia da base.",
            "controle": "text",
            "opcoes": [],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (text). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "mensagens-progressivas",
            "nome": "Mensagens progressivas",
            "descricao": "Alterna uma sequência de mensagens de etapa ao longo do tempo em operações longas, dando contexto do que está acontecendo.",
            "controle": "multi",
            "opcoes": [
                "Conectando…",
                "Processando…",
                "Baixando…",
                "Quase lá…",
                "Finalizando…"
            ],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "tempo-decorrido",
            "nome": "Tempo decorrido",
            "descricao": "Exibe um contador ao vivo (ex.: '12s') ao lado do spinner para sinalizar que a operação segue viva.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "timeout-com-aviso",
            "nome": "Timeout com aviso",
            "descricao": "Após N segundos sem concluir, revela uma mensagem de 'está demorando mais que o esperado / tentar novamente'.",
            "controle": "number",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (number). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "overlay-bloqueante",
            "nome": "Overlay bloqueante",
            "descricao": "Centraliza o spinner sobre a região-alvo com um scrim que bloqueia a interação enquanto carrega.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "bot-o-cancelar",
            "nome": "Botão cancelar",
            "descricao": "Adiciona uma ação de cancelar ao lado do spinner para abortar operações canceláveis.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": true,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        },
        {
            "id": "an-ncio-de-conclus-o",
            "nome": "Anúncio de conclusão",
            "descricao": "Ao resolver o carregamento, anuncia uma mensagem de 'Concluído' para leitores de tela, complementando o role=status congelado.",
            "controle": "toggle",
            "opcoes": [],
            "posicionavel": false,
            "e_funcao": true,
            "como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/spinner; comportamento via controller Stimulus. Não altera a base."
        }
    ],
    "snippet_uso": "<twig:Spinner />",
    "exemplo_demo": "<div class=\"flex w-full max-w-xs flex-col gap-4 [--radius:1rem]\">\n    <twig:Item variant=\"muted\">\n        <twig:Item:Media>\n            <twig:Spinner />\n        </twig:Item:Media>\n        <twig:Item:Content>\n            <twig:Item:Title class=\"line-clamp-1\">Processing payment...</twig:Item:Title>\n        </twig:Item:Content>\n        <twig:Item:Content class=\"flex-none justify-end\">\n            <span class=\"text-sm tabular-nums\">$100.00</span>\n        </twig:Item:Content>\n    </twig:Item>\n</div>",
    "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": "Ícone lucide:loader-2 (a forma do indicador), animate-spin (giro e sua velocidade), size-4 (tamanho fixo), cor herdada via currentColor (sem cor própria), cantos/raio e ausência de espaçamento/moldura próprios, data-slot=\"spinner\", e a semântica de a11y congelada: role=\"status\" + aria-label (default 'Loading')",
    "mcp": {
        "tool": "get_component",
        "args": {
            "id": "spinner"
        }
    }
}