Stat
ProntoKPI display: número grande + label + delta opcional. Foco no número (display tipo) com unidade e contexto sutis.
Usar quando
Dashboard com indicadores. Cards de resumo (tarefas pendentes, valor recebido, prazo médio). Comparação com período anterior.
Não usar quando
Texto descritivo (use parágrafo). Múltiplas dimensões (use Chart). Status binário (use Badge).
Variantes
Microinterações
| Microinteração | Disparada por | Comportamento | Timing |
|---|---|---|---|
| Count-up animation | mount com value alvo | Conta de 0 a value em easing ease-out | 600-1200ms |
| Delta pop | value muda | Delta scale 1→1.15→1 | 300ms cubic-bezier(0.32,0.72,0,1) |
| Tabular nums | render | Números alinhados decimal (sem dance ao mudar) | static |
| Sparkline draw | mount | Path SVG desenha da esquerda pra direita | 800ms ease |
Acessibilidade
Acessibilidade — checklist
ARIA esperado
- role="figure" para o stat completo
- <dt> label / <dd> value se semântica de definition list
- aria-label="Tarefas concluídas: 247 (subiu 18% em relação ao mês anterior)"
- role="img" no sparkline com aria-label descrevendo a tendência
Notas
- Use SEMPRE font-mono + tabular-nums em números (alinhamento decimal).
- Delta tone: up=verde se positivo, down=carmim se negativo, neutro=brass se mudou mas sem juízo.
- Para acessibilidade, número grande precisa de label menor mas legível.
- Em mobile, reduza display de 3xl para xl/2xl.
Código
'use client';
import { ArrowUp, ArrowDown } from 'lucide-react';
import { T, FONT, TYPE, SP } from '@/lib/tokens';
interface StatProps {
label: string;
value: string;
prefix?: string;
suffix?: string;
delta?: string;
deltaTone?: 'up' | 'down' | 'neutral';
}
export function Stat({ label, value, prefix, suffix, delta, deltaTone }: StatProps) {
const tone = deltaTone === 'up' ? T.verde : deltaTone === 'down' ? T.carmim : T.brass;
return (
<div role="figure" aria-label={`${label}: ${prefix ?? ''}${value}${suffix ?? ''}`}>
<p style={{
fontSize: TYPE.xs, fontWeight: 500, letterSpacing: '0.04em',
textTransform: 'uppercase', color: T.inkSubtle, fontFamily: FONT.mono,
}}>{label}</p>
<div style={{ display: 'flex', alignItems: 'baseline', gap: SP[2] }}>
{prefix && <span style={{ fontSize: TYPE.lg, color: T.inkMuted }}>{prefix}</span>}
<span style={{
fontFamily: FONT.display, fontSize: TYPE['3xl'], fontWeight: 600,
color: T.ink, fontVariantNumeric: 'tabular-nums',
}}>{value}</span>
{suffix && <span style={{ fontSize: TYPE.lg, color: T.inkMuted }}>{suffix}</span>}
</div>
{delta && (
<span style={{ fontSize: TYPE.sm, color: tone, fontWeight: 500, fontFamily: FONT.mono }}>
{deltaTone === 'up' && <ArrowUp size={11} />}
{deltaTone === 'down' && <ArrowDown size={11} />}
{delta}
</span>
)}
</div>
);
}Regras
Faça
- ✓Número em display tipo grande (Source Serif 28-40px).
- ✓Label em mono uppercase pequeno (TYPE.xs com letter-spacing 0.04em).
- ✓tabular-nums obrigatório em números (alinhamento decimal).
- ✓Delta com seta direcional + cor semântica (verde/carmim/brass).
- ✓Prefix R$ / suffix % alinhados na baseline do número.
- ✓Count-up animation no mount (max 1200ms).
Não faça
- ✗Não use números sem tabular-nums (números dançam ao atualizar).
- ✗Não use cor decorativa no número (sempre ink — delta tem cor).
- ✗Não force display sem hierarquia (label sempre presente).
- ✗Não tenha 5+ stats lado-a-lado (caos).
- ✗Não use delta como ÚNICO indicador (sempre acompanha o valor).
Tokens usados
| Token | Valor | Papel |
|---|---|---|
T.ink | #0A0A0A | cor do número |
T.inkMuted | #5A5A5E | prefix/suffix |
T.inkSubtle | #8A8A8D | label |
T.verde | #2F6B3C | delta up |
T.carmim | #A32E2E | delta down |
T.brass | #A47C2B | delta neutral |
FONT.display | Source Serif Pro | número grande |
FONT.mono | JetBrains Mono | label + delta |
TYPE.3xl | 40 | fontSize do número |