Timeline

Follow what happened, newest first, grouped by day.

import { Timeline } from "@sagui/ui";
Markdown
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/ui

Usage

example.tsx
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.