Spinner

Pronto

Loader circular indeterminate. Indica que algo está acontecendo sem comunicar progresso medível.

Usar quando

Loading curto (300ms-3s). Async sem progresso conhecido (fetch, save). Inline em botão durante mutation.

Não usar quando

Loading com progresso medível (use Progress Bar). Loading inicial de listas (use Skeleton). Loading >5s (use mensagem ou estimativa).

Variantes

Tamanhos
Cores
Inline com texto (loading state)
Carregando processos...
Em botão (mutation)
Centralizado em container (page loading)

Sincronizando intimações...

Microinterações

MicrointeraçãoDisparada porComportamentoTiming
Continuous spinmountrotate 0deg → 360deg infinite linear600ms linear loop
Reduced motionprefers-reduced-motionPulse opacity 1→0.5→11500ms ease loop
Mount faderenderopacity 0→1150ms ease

Acessibilidade

Acessibilidade — checklist

ARIA esperado
  • role="status" + aria-label="Carregando"
  • aria-live="polite" no container pai
  • aria-hidden="true" se decorativo (label visível ao lado)
Notas
  • Sempre tenha texto adjacente ("Carregando...") — spinner sozinho não fala.
  • Em reduced-motion: pulse em vez de spin.
  • Tamanho proporcional ao contexto (xs em chip, lg em página inteira).
  • Cor: ink default, brass para destaque, surface em fundo escuro.

Código

'use client';
import { T } from '@/lib/tokens';

interface SpinnerProps { size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; color?: 'ink' | 'brass' | 'surface' | 'muted' }

const SIZES = { xs: 12, sm: 14, md: 18, lg: 28, xl: 40 };

export function Spinner({ size = 'md', color = 'ink' }: SpinnerProps) {
  const px = SIZES[size];
  return (
    <span role="status" aria-label="Carregando" style={{
      display: 'inline-block', width: px, height: px,
      border: `${px > 20 ? 2.5 : 1.5}px solid rgba(0,0,0,0.1)`,
      borderTopColor: T[color] ?? T.ink, borderRadius: '50%',
      animation: 'lb-spinner-spin 600ms linear infinite',
    }} />
  );
}

Regras

Faça

  • Spin contínuo 600ms linear loop.
  • Ring com border-top-color (apenas 1/4 do círculo colorido).
  • Sempre tenha texto label adjacente.
  • Reduced-motion: pulse em vez de spin.
  • Tamanho proporcional (xs em badge, xl em loading de página).

Não faça

  • Não use spin para loading >5s (frustra — use estimativa).
  • Não tenha múltiplos spinners visíveis ao mesmo tempo.
  • Não use cor decorativa (ink padrão, brass se contexto editorial).
  • Não esqueça aria-label.
  • Não tenha spinner sem context (sempre acompanhe de texto).

Tokens usados

TokenValorPapel
T.ink#0A0A0Acor default do ring colorido
T.brass#A47C2Bvariante brass
T.surface#FFFFFFspinner sobre fundo escuro
T.inkMuted#5A5A5Evariante muted