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
Capacidades 8
Conteúdo
Atraso de exibição
Só renderiza o spinner após X…
Rótulo visível
Mostra o texto do aria-label também…
Mensagens progressivas
Alterna uma sequência de mensagens de…
Tempo decorrido
Exibe um contador ao vivo (ex.:…
Overlay bloqueante
Centraliza o spinner sobre a região-alvo…
Botão cancelar
Adiciona uma ação de cancelar ao…
Comportamento
Timeout com aviso
Após N segundos sem concluir, revela…
Acessibilidade
Anúncio de conclusão
Ao resolver o carregamento, anuncia uma…
Claude Code
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')
LLM / MCP
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"
}
}
}