Skeleton

Pronto

Placeholder animado durante carregamento. Reproduz o shape do conteúdo final para evitar layout shift.

Usar quando

Loading inicial de listas, cards, tabela. Async de dados que demora >300ms. Streaming de conteúdo.

Não usar quando

Loading instantâneo (<300ms — só piscaria). Spinner indeterminate (use Spinner). Form submit (use Button loading).

Variantes

Linha de texto
Card placeholder
Lista (5 linhas de tabela)
Avatar circular
Stat card placeholder

Microinterações

MicrointeraçãoDisparada porComportamentoTiming
ShimmermountGradiente brass-tint move da esquerda pra direita continuamente1500ms ease-in-out infinite
Fade out (replace)dados chegamSkeleton fade-out + conteúdo real fade-in200ms cross
Soft pulse fallbackreduced-motionOpacity pulse 1→0.5→1 em vez de shimmer1500ms loop

Acessibilidade

Acessibilidade — checklist

ARIA esperado
  • aria-busy="true" no container pai
  • role="status" + aria-label="Carregando..."
  • aria-live="polite" para anunciar quando dados chegam
Notas
  • Reproduzir shape exato do conteúdo final (largura, altura, alinhamento) — evita layout shift.
  • Em reduced-motion, use pulse em vez de shimmer.
  • Não use skeleton para loading curto (<300ms) — apenas pisca.
  • Combine com Suspense (React) ou loading.tsx (Next.js).

Código

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

interface SkeletonProps {
  width?: number | string;
  height?: number | string;
  radius?: 'sm' | 'md' | 'lg' | 'full';
}

export function Skeleton({ width = '100%', height = 14, radius = 'sm' }: SkeletonProps) {
  const r = radius === 'full' ? 980 : radius === 'lg' ? RADIUS.lg : radius === 'md' ? RADIUS.md : RADIUS.sm;
  return (
    <div
      role="status"
      aria-label="Carregando"
      style={{
        width, height,
        borderRadius: r,
        background: `linear-gradient(90deg, ${T.surface3} 0%, ${T.surface2} 50%, ${T.surface3} 100%)`,
        backgroundSize: '200% 100%',
        animation: 'lb-shimmer 1500ms ease-in-out infinite',
      }}
    />
  );
}

Regras

Faça

  • Reproduza shape exato do conteúdo (mesma largura, altura, gap).
  • Shimmer brass-tint sutil da esquerda pra direita (1500ms loop).
  • Em reduced-motion: pulse em opacity em vez de shimmer.
  • aria-busy + aria-label="Carregando" no container.
  • Show só após 300ms de delay (evita flash).

Não faça

  • Não use spinner em vez de skeleton para listas (skeleton é melhor UX).
  • Não force animação agressiva (shimmer rápido cansa).
  • Não esqueça de remover quando dados chegarem (cross-fade).
  • Não use cor brass forte (deve ser sutil).
  • Não use forma genérica de skeleton (deve parecer com o conteúdo final).

Tokens usados

TokenValorPapel
T.surface3#F3F0E6cor base do skeleton
T.surface2#FAF8F2cor de highlight do shimmer
RADIUS.sm6borderRadius default
RADIUS.pill980radius=full (avatar)