Timeline
ProntoSequência cronológica vertical de eventos. Ideal para andamentos processuais, histórico de mudanças, auditoria.
Usar quando
Andamentos do processo, histórico de comunicação, log de auditoria, etapas concluídas vs pendentes.
Não usar quando
Lista plana sem ordem temporal (use list). Sequência horizontal (use Stepper).
Variantes
Microinterações
| Microinteração | Disparada por | Comportamento | Timing |
|---|---|---|---|
| Mount stagger | lista monta | Cada item aparece 60ms após o anterior, fade-in | cumulativo (60ms × N) |
| Active pulse | item current | Bullet expande sutilmente (1→1.1→1) | 2s ease infinite |
| Hover item | mouseenter | Background do conteúdo → surfaceHover | 150ms ease |
| Expand details | click no item | Conteúdo expande accordion-style | 250ms cubic-bezier(0.32,0.72,0,1) |
Acessibilidade
Acessibilidade — checklist
ARIA esperado
- <ol> em vez de <ul> (ordem importa)
- <li> com <time dateTime="ISO" /> em cada item
- role="list" implícito
- Item current: aria-current="step" ou aria-current="true"
Notas
- Use <ol> (ordered list) — ordem temporal importa.
- <time dateTime="2026-05-15T14:30"> em cada timestamp para SEO/acessibilidade.
- Status (concluído, em andamento, pendente) com cor + ícone (não só cor).
- Em mobile, mantenha indentação reduzida para conteúdo respirar.
Código
'use client';
import { CheckCircle2, Circle } from 'lucide-react';
import { T, TYPE, SP } from '@/lib/tokens';
interface TimelineItem {
date: string;
title: string;
description?: string;
icon?: React.ComponentType;
status?: 'done' | 'current' | 'pending';
}
export function Timeline({ items }: { items: TimelineItem[] }) {
return (
<ol style={{ listStyle: 'none', padding: 0, margin: 0 }}>
{items.map((item, i) => {
const Icon = item.icon ?? Circle;
const isLast = i === items.length - 1;
return (
<li key={i} style={{ position: 'relative', paddingLeft: SP[8], paddingBottom: isLast ? 0 : SP[5] }}>
{!isLast && (
<span style={{ position: 'absolute', left: 11, top: 24, bottom: 0,
width: 1, background: T.borderFaint }} />
)}
<div style={{ position: 'absolute', left: 0, top: 2, width: 24, height: 24,
borderRadius: 980, background: T.surface, border: `1.5px solid ${T.border}`,
display: 'grid', placeItems: 'center', color: T.inkMuted }}>
<Icon size={12} />
</div>
<time dateTime={item.date} style={{ fontSize: TYPE.xs, color: T.inkSubtle, fontFamily: 'var(--lb-font-mono)' }}>
{item.date}
</time>
<p style={{ fontSize: TYPE.base, color: T.ink, fontWeight: 500, margin: '2px 0 0 0' }}>
{item.title}
</p>
{item.description && (
<p style={{ fontSize: TYPE.sm, color: T.inkMuted, margin: '4px 0 0 0' }}>
{item.description}
</p>
)}
</li>
);
})}
</ol>
);
}Regras
Faça
- ✓Use <ol> (ordem temporal importa).
- ✓<time dateTime="ISO"> em cada timestamp.
- ✓Linha vertical conecta itens (não desenha no último).
- ✓Bullet 24×24 com ícone de status semântico.
- ✓Status com cor + ícone (não só cor).
- ✓Mais recente em cima OU embaixo — escolha um e mantenha.
Não faça
- ✗Não use <ul> (sequência precisa de <ol>).
- ✗Não esconda timestamps (data é o ponto).
- ✗Não mostre mais que ~10 itens visíveis sem paginação/expand.
- ✗Não use horizontal para muitos itens (use Stepper limitado a 5-7).
- ✗Não force ícones decorativos (use só se semântico).
Tokens usados
| Token | Valor | Papel |
|---|---|---|
T.surface | #FFFFFF | background do bullet |
T.border | #E3DFD2 | borda do bullet |
T.borderFaint | #F0EEE5 | linha vertical conectora |
T.ink | #0A0A0A | bullet quando current/done |
T.brass | #A47C2B | ring sutil em current |
T.verde | #2F6B3C | check de done |
FONT.mono | JetBrains Mono | timestamps |