---
fonte: 01-canonicos/seed-componentes.md
versao_da_fonte: v1.43
secao: 18
titulo: "Spinner — `estável` · validado pelo Rafael em 2026-08-04 (2 ciclos de crítica → emendas R3-b/R3-c; suite v0.17)"
sequencia: 20 de 98
bytes_do_corpo: 6280
md5_do_corpo: d67c84a818f3ac458efc90a4e66ddf4a
gerado_por: 06-validacao/geradores/gen-camada-ia.py
nota: fatia GERADA — o corpo abaixo é byte a byte o trecho do canônico; edite o canônico, nunca esta fatia. Canônico inteiro em https://ds.seed.eng.br/01-canonicos/seed-componentes.md
---
## 18. Spinner — `estável` · validado pelo Rafael em 2026-08-04 (2 ciclos de crítica → emendas R3-b/R3-c; suite v0.17)

> **Base (3 rodadas, 2026-08-04):** R1 — NN/g (progress indicators: <1s nada, loop 2–10s; skeleton 101), Carbon (loading lg/sm, inline loading 4 estados, um indicador por vez), Smashing. R2 — React Aria useProgressBar (spinner = progressbar circular), shadcn/Radix, EUI (herança). R3 — SAP Fiori busy indication (delay padrão de 1s em todos os controles; bloquear o mínimo), EC Europa (sempre com mensagem; ≤1 lg por página; nunca <1s), issue de a11y do UI5 (equivalente textual anunciado), aria-busy (escopo = a região que muda; segura anúncios parciais).

### 18.1 A RÉGUA DE ESPERA (transversal do sub-bloco B — governa §18–§20; análoga à régua do §7.1)

| Espera | Indicador |
|---|---|
| **< 1s** | **NADA** — indicador que pisca é ruído (NN/g; Fiori: delay padrão 1s; EC) |
| **1–10s, fração desconhecida** | **Spinner** para ação/processo local · **skeleton** para carga de ESTRUTURA de conteúdo |
| **> 10s ou fração conhecida** | **Progress determinate** + texto de status; pode nascer indeterminada e converter (Carbon/m2) |

**R3-b (emenda da validação visual do Rafael, 2026-08-04 — supersede parcial do R3): a ENTRADA do indicador tem TRÊS regimes, não um.** A crítica "o delay de 1s vai parecer travado" foi medida contra a evidência e está correta em dois dos três casos — o R3 original colapsava situações distintas:

1. **Ação do usuário** (clicar salvar, alternar switch): reconhecimento IMEDIATO ≤100ms — o próprio controle entra em loading (spinner sm do §1), SEM delay. Fonte: os 3 limites de Nielsen (0,1s = reação instantânea obrigatória; o limite de 1s pressupõe que o clique JÁ foi reconhecido); Carbon inline loading mostra o estado ativo imediatamente após a ação.
2. **Carga inicial de tela/região VAZIA:** skeleton IMEDIATO, sem delay — não há UI antiga para segurar a percepção; 1s de branco lê como travamento. Fonte: Carbon usa skeleton "on initial page load"; o delay do Fiori pressupõe explicitamente "mostrar a UI por um segundo" — ou seja, UI existente.
3. **Refresh sobre conteúdo EXISTENTE** (tabela recarregando, filtro aplicado): delay de 1s antes do indicador (Fiori/EC/NN/g) — a UI antiga permanece visível e utilizável; flash de overlay em resposta rápida seria pior que a espera.

**R3-c (2ª crítica do Rafael na mesma validação — "o refresh lento ainda demora"): os regimes COMPÕEM, não se excluem.** O regime 3 quase nunca existe sozinho: refresh de região no ERP quase sempre nasce de uma AÇÃO do usuário (filtro, recarregar) — e então o regime 1 SE APLICA JUNTO: o controle disparador reconhece em ≤100ms (loading do §1) e o indicador de REGIÃO entra só se a espera passar de 1s. O vácuo percebido não era o delay — era refresh sem ack no controle, que a régua agora PROÍBE: **toda espera do regime 3 disparada por ação exige ack imediato no controle disparador.** Refresh iniciado pelo sistema (dado chegando por conexão viva) é o único caso de delay sem ack — e ali o usuário não pediu nada, então não há clique órfão. Nielsen fecha: o limite de 1s sem indicador pressupõe o clique reconhecido em ≤0,1s. O delay de 1s fica parametrizado (`--seed-wait-delay`, default 1000ms) e será revisitado com telemetria real do ERP na Fase 4 — pendência declarada, não decisão aberta.

Complemento mantido: uma vez exibido, permanência mínima de ~400ms (anti-flash de saída — inferência de engenharia declarada). Descartado: delay único de 1s para tudo (o R3 original — reprovado na validação visual); regime 3 sem ack no controle (reprovado na 2ª crítica).

### 18.2 Decisões R1–R6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| R1 | Dois tamanhos com papéis: **lg** para seção/página com overlay semitransparente BLOQUEANTE; **sm** inline (célula, linha) — e o spinner do botão É o loading do §1 (herda) | Carbon: lg = takeover com overlay; inline desabilita os controles envolvidos contra ação repetida | Tamanho único |
| R2 | NUNCA nu: sempre frase junto ("Carregando propostas…"); máx. UM lg por página | EC: sempre com mensagem; não mais de um por página (salvo sm em componentes); UI5 a11y: equivalente textual anunciado | Spinner mudo |
| R3 | Delay 1s cancelável — **restrito ao regime 3 (refresh sobre conteúdo existente); ver emenda R3-b no §18.1** para ação do usuário (imediato no controle) e carga inicial (skeleton imediato) | Fiori/EC/NN/g, contextualizados pela emenda | Exibição imediata em refresh (flash); delay único para tudo (R3 original, superseded pela R3-b) |
| R4 | ARIA: SVG decorativo (`aria-hidden`); a região que carrega recebe `aria-busy="true"` (segura anúncios parciais) + live region `role="status"` pré-existente (1.13-b) anuncia UMA frase no início e UMA no fim ("Propostas carregadas") | aria-busy: um anúncio coerente ao voltar a false; sem live region + indicador visível, ninguém percebe nada | Anunciar cada mutação; role no SVG |
| R5 | `prefers-reduced-motion`: sem rotação — indicador estático + a frase carrega o sinal | WCAG 2.3.3; padrão §0.5/§8.3 | Rotação sempre |
| R6 | UM sinal de espera por região: nunca spinner sobre skeleton nem dois indicadores no mesmo alvo | Carbon: evitar múltiplos simultâneos; redundância é ruído | Empilhar sinais |

### 18.3 Anatomia e tokens

lg 32px / sm 16px; traço 3px/2px em `--seed-action-primary-bg` sobre trilho `--seed-border-subtle`; rotação 900ms linear contínua; overlay do lg: página com véu (`--seed-surface-page` a 60%) + spinner centrado + frase abaixo. Frase em `--seed-text-secondary`.

### 18.4 Os 7 testes

1. Traço sobre trilho ≥3:1 (medir). 2. Espera sinalizada por movimento + frase (nunca só cor). 3. Grayscale: movimento + frase carregam. 4. lg×sm distinguíveis pelo escopo (overlay × inline). 5. Um lg por página; uma live region por região. 6. Estado visível: a frase diz O QUE carrega. 7. 360px: overlay cobre a viewport com frase legível; sm cabe na linha de tabela densa. *(Verificado: suite v0.17, aprovado.)*

---

