Timeline
Follow what happened, newest first, grouped by day.
import { Timeline } from "@sagui/ui";Loading demo
When to use
- Project history, audit logs, and deploy streams where recency matters.
- Live feeds where new updates slide in at the top.
- Rows that expand in place to show logs or detail.
When not to use
- Use sortable-data-table when people sort or compare.
- Use stepper for progress through fixed steps.
Installation
Install the package and import the styles once. The installation guide covers the Tailwind setup.
$ npm install @sagui/uiUsage
import { Timeline } from "@sagui/ui";
const events = [
{ id: "e1", at: "2026-09-22T09:12:00Z", actor: "Maya", title: "merged Checkout redesign into main", meta: "PR #482" },
{ id: "e2", at: "2026-09-22T08:40:00Z", title: "Deploy failed", tone: "danger" as const, detail: <pre>Build step exited 1</pre> },
];
export function Activity({ now }: { now: number }) {
return <Timeline events={events} now={now} label="Project activity" maxHeight={420} />;
}Examples
New updates arrive
Newest shows first. When an update arrives the feed scrolls back to the top, unless scrollToNew is false.
Loading demo
API reference
Timeline
A vertical activity feed grouped by day with pinned day labels and rows that expand in place.
| Prop | Type | Default | Description |
|---|---|---|---|
events (required) |
{ id: string; at: string | number; actor?: string; title: string; meta?: string; detail?: ReactNode; avatar?: string; icon?: ReactNode; tone?: "neutral" | "success" | "danger" }[] |
– | Updates in any order; newest shows first. Rows with detail are expandable. |
now (required) |
number |
– | Reference time in epoch ms for relative labels and day groups. Pass a ticking clock to keep labels fresh. |
label (required) |
string |
– | Accessible name for the feed. |
timeZone |
string |
"UTC" |
Time zone for day groups and clock times. |
locale |
string |
"en-US" |
Formatting locale. |
maxHeight |
number | string |
– | Height of the scrolling area. Without it the feed grows with the page. |
scrollToNew |
boolean |
true |
Scrolls back to the top when a new update arrives. |
defaultExpanded |
string[] |
[] |
Event ids expanded on mount. |
headingLevel |
2 | 3 | 4 | 5 | 6 |
3 |
Heading level for day labels. |
className |
string |
– | Class for the root. |
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowDownorArrowUp | Moves between expandable rows. |
| HomeorEnd | Jumps to the first or last expandable row. |
| EnterorSpace | Expands or collapses the focused row. |
Accessibility
- The feed is a labelled role="region"; day labels are headings at headingLevel with a hidden update count.
- Expandable rows are buttons with aria-expanded and aria-controls; rows without detail are not interactive.
- Times use a time element with the full date in hidden text, and new updates are announced in a status region.
- Say the outcome in the title, since tone is color only.
Motion
- The connecting line draws as rows come into view and markers pop in.
- New updates slide in at the top while the rest glide down; details expand on a spring.
- Reduced motion replaces travel with short fades and instant height changes.
Responsive behavior
- Day labels stay pinned while updates scroll under them, inside maxHeight or the page.
- Below 420px row padding tightens so text keeps its width.
Performance
- Events are not virtualized; set maxHeight for long feeds and trim old events.
- Pass a now that ticks about once a minute rather than every second, since it re-renders every row.
Notes
- Use for project history, audit logs, and deploy streams where order and recency matter. Use sortable-data-table when people sort or compare.
- Pass a ticking now (for example updated every minute) so relative times stay current, and a fixed timeZone to avoid hydration mismatches.