Timeline
A read-only chronology — a toned-marker rail of ordered events for an activity feed, audit log, order history, or changelog.
@garn/ui/timelineOn this page
Default
Terse items — title + time + tone, the order-activity feed.
Tones
The full palette — neutral · brand · info · success · warning · danger · muted.
Markers
Marker geometry — dot · outline · icon.
Sizes
sm · md · lg marker ladders.
Current
A `current` item with a tonal halo — a live shipment tracker.
Pending
A loading tail — spinner marker + a fading connector.
Rich
Composed items — avatar markers, a collapsible body, and an actions row.
Grouped
Timeline.Group day headers (Today / Yesterday) over nested lists.
Alternating
side="alternating" — a zig-zag roadmap.
End Side
side="end" — the rail on the trailing edge.
Dense
dense + sm — a tight system-event log.
Skeleton
Timeline.Skeleton — the loading preset in the rail's geometry.
Empty
An Empty state inside a Timeline with no events.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add timeline- Registry
timeline- Deps
class-variance-authority@radix-ui/react-slot- Registry deps
utilsspinner
Import
import { Timeline.Item, Timeline.Group, Timeline.Separator, Timeline.Marker, Timeline.Connector, Timeline.Content, Timeline.Title, Timeline.Time, Timeline.Body, Timeline.Actions, Timeline.Opposite, Timeline.Skeleton } from "@garn/ui/timeline";Anatomy
The parts this component is built from — compose them to assemble it.
Timeline.ItemRequired[data-slot="timeline-item"]One event — a <li>. Dual-mode: terse (title / time / tone / marker / variant / loading / current / side) or composed children. Reflects data-state (loading | current | complete).
Timeline.Group[data-slot="timeline-group"]A dated/section run — an <li> header (optionally sticky) over a nested <ol> of its items; valid HTML that AT announces as a sub-list.
Timeline.Separator[data-slot="timeline-separator"]The fixed-width marker + connector column (decorative, aria-hidden) that keeps every marker on one axis.
Timeline.Marker[data-slot="timeline-marker"]The dot / icon / outline ring at the head of an item (carries the variant axis). Decorative; a loading item shows a Spinner.
Timeline.Connector[data-slot="timeline-connector"]The vertical line tying an item to the next; hidden on the last item; fades after a pending item.
Timeline.Content[data-slot="timeline-content"]The text column (title · time · body · actions).
Timeline.Title[data-slot="timeline-title"]The event title (asChild to render a heading or link).
Timeline.Time[data-slot="timeline-time"]The timestamp — a real <time datetime> carrying the machine ISO value beside the human text.
Timeline.Body[data-slot="timeline-body"]The event body; when collapsible, clamps to lines with a real <button aria-expanded> disclosure.
Timeline.Actions[data-slot="timeline-actions"]A trailing row of normal focusable controls (the <li> itself isn't interactive).
Timeline.Opposite[data-slot="timeline-opposite"]The meta rail on the far side of the marker (typically the timestamp in an alternating layout).
Timeline.Skeleton[data-slot="timeline-skeleton"]A loading preset — marker + connector + 2-line placeholders in the rail's exact geometry.
Props
The component's public props and their types.
side- Description
- Marker/content placement: left rail, right rail, or zig-zag.
- Type
"alternating" | "end" | "start"- Default
"start"
size- Description
- Marker geometry ladder (identity — does not shrink with density).
- Type
"lg" | "md" | "sm"- Default
"md"
tone- Description
- Default marker tone inherited by every item (each item may override).
- Type
"brand" | "danger" | "info" | "muted" | "neutral" | "success" | "warning"- Default
"neutral"
variant- Description
- No description.
- Type
"dot" | "icon" | "outline"- Default
dot
dense- Description
- Tighten type + line-height for log-style feeds (spacing already follows density).
- Type
boolean- Default
false
label- Description
- Convenience accessible name for the list (the `<ol>`).
- Type
React.ReactNode
Plus 283 inherited native <ol> attributes.
Timeline.Item
current- Description
- Emphasise this entry as the live/active one (a tonal halo).
- Type
boolean- Default
false
dateTime- Description
- Machine-readable ISO timestamp for the `<time datetime>` attribute.
- Type
string
loading- Description
- This entry is in progress — the marker shows a spinner, the connector fades out.
- Type
boolean- Default
false
marker- Description
- Custom marker content (icon / avatar / glyph). Overrides the default dot.
- Type
React.ReactNode
side- Description
- Per-item side override (ignored unless the layout is alternating).
- Type
TimelineResolvedSide
time- Description
- Terse mode: the timestamp (rendered in a `<time>`).
- Type
React.ReactNode
title- Description
- Terse mode: the event title.
- Type
React.ReactNode
tone- Description
- Marker/text tone. Overrides the Timeline default.
- Type
"brand" | "danger" | "info" | "muted" | "neutral" | "success" | "warning"
value- Description
- Stable key (reserved for selection/anchoring).
- Type
string
variant- Description
- Marker geometry: a plain dot (default), a tonal icon ring, or an outline ring.
- Type
"dot" | "icon" | "outline"
Plus 278 inherited native HTML attributes.
Timeline.Group
label- Description
- Section header text (e.g. "Today", "Yesterday", a date).
- Type
React.ReactNode
sticky- Description
- Stick the header to the top of the scroll container while its run is visible.
- Type
boolean- Default
false
Plus 281 inherited native HTML attributes.
Timeline.Title
asChild- Description
- Render as the child element (e.g. a heading or a link), merging props.
- Type
boolean- Default
false
Plus 280 inherited native HTML attributes.
Timeline.Time
dateTime- Description
- Machine-readable ISO value for the `datetime` attribute.
- Type
string
Plus 279 inherited native HTML attributes.
Timeline.Body
collapsible- Description
- Clamp to `lines` with a "Show more" disclosure (uncontrolled).
- Type
boolean- Default
false
lines- Description
- Number of lines to show when collapsed.
- Type
number- Default
2
showLessLabel- Description
- Label for the collapse control.
- Type
string- Default
"Show less"
showMoreLabel- Description
- Label for the expand control.
- Type
string- Default
"Show more"
Plus 280 inherited native HTML attributes.
Timeline.Skeleton
dense- Description
- Tighten spacing to match a dense feed.
- Type
boolean- Default
false
rows- Description
- How many placeholder rows to render.
- Type
number- Default
3
size- Description
- Marker geometry ladder — match the Timeline it stands in for.
- Type
TimelineSize- Default
"md"
Plus 283 inherited native HTML attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="timeline"][data-slot="timeline-label"]States
| Selector | State | Description |
|---|---|---|
[data-state=current] | current | An item marked `current` (the live/active one) gets a tonal halo on its marker, like Stepper's current step. |
[data-state=loading] | loading | A pending item — the marker shows a Spinner and the trailing connector fades out (gradient to transparent). |
[data-dense] | dense | Tighter type + line-height for log-style feeds. |
| Property | Token | Tier |
|---|---|---|
gap | --garn-gap-stack | semantic · air |
markerFill | --garn-badge-neutral-boldbrandable | component |
Private instance vars (never externalize): --m-bg
When to use
A read-only chronology of events that already happened — an activity feed, audit log, order/shipment history, changelog, or status thread. Reach for the terse <Timeline.Item> per event; compose the slots for rich entries.
Reach for something else when
- An interactive, gated, forward-moving process where the user navigates steps (use Stepper)
- a flat list of non-temporal items (use a plain list)
- a single 'field: value' detail block (use DataList)
- progress toward one goal (use Progress).
Overview
Timeline shows a sequence of events along a single axis — an activity feed, an order's status history, an audit trail, a changelog. Timeline.Item is one event (terse props or composed children, reflecting loading / current / complete), Timeline.Group is a dated/section run, and the Separator / Marker / Connector keep every marker aligned on one line. Use it for chronological events; for ordered steps the user completes use a Stepper, and for plain term→value facts use DataList.
Guidelines
Use it for events in time, not for steps to take. A timeline records what happened (or is happening); a Stepper drives a process forward. Order consistently — usually newest-first for feeds, oldest-first for histories — and stick to it.
Give each event a clear marker and timestamp. Use the marker variant/tone to convey state (done, current, error) and show a human-readable, locale-aware time; the current/loading states highlight what's live. Group long runs by date with Timeline.Group.
Keep it scannable and accessible. Lead each item with what changed, keep detail terse, and let the marker column stay decorative (aria-hidden) while the text carries the meaning to assistive tech. Don't overload one axis with unrelated streams.
Best practices
- Give the list an accessible name (label / aria-label).
- Pass
dateTime(ISO) on each item so the <time> carries a machine value beside the human text. - Use
tonefor event status — it announces an sr-only word AND colours the marker from the shared badge vocabulary. - Use Timeline.Group for day/section runs (optionally
sticky); useloadingfor the live pending tail. - Order items the way they should read (the component preserves the consumer's array order — no auto-sort).
- Don't use Timeline for a process the user steps through — that's Stepper (it has aria-current, focusable triggers, and gated movement).
- Don't rely on marker colour alone for meaning — keep the tone (it carries the sr-only status word).
- Don't strip the focus ring on item actions or the collapsible-body toggle.
- Don't hardcode marker colours — tones resolve through the --garn-badge-* tokens (edit dark mode in the token layer).
Content guidelines
- Keep titles short and factual; put detail in Timeline.Body (make it
collapsiblefor long audit payloads). - Write timestamps human-readable but always pass the ISO
dateTimefor the machine value.
Troubleshooting
Reaching for Timeline to build a multi-step wizard / checkout flow.
Cause. Timeline is a past-facing record; a forward, gated process is Stepper.
Fix. Use Stepper for interactive processes (it has aria-current, focusable triggers, gated movement); Timeline for history.
Screen readers don't announce an event's status.
Cause. The marker is decorative — status rides in the tone's sr-only word, and neutral/brand/muted carry none.
Fix. Set a status tone (info/success/warning/danger) so the sr-only word is announced; don't rely on colour.
The <time> has no machine-readable value.
Cause. Only visible text was passed; the ISO value comes from dateTime.
Fix. Pass dateTime (e.g. "2026-06-28T14:20") on each item alongside the human time.
A dev console warns the Timeline has no accessible name.
Cause. No label, aria-label, or aria-labelledby.
Fix. Pass label (rendered + wired) or an aria-label / aria-labelledby.
Accessibility
- Role
list- Focus
- The list and items aren't focusable; only their actions and the collapsible-body toggle are, each with a 2px --garn-ring focus ring.
Accessibility requirements
Give the Timeline an accessible name via `label`, `aria-label`, or `aria-labelledby`.
when a Timeline has no `label`, `aria-label`, or `aria-labelledby`
- A real <ol> of <li> events — an ordered list of facts, NOT a navigation. There is deliberately no aria-current, no roving focus, and no panels (the Stepper distinction).
- Each timestamp is a <time datetime> carrying a machine ISO value beside the human text (pass `dateTime`; otherwise the visible text is used as a best-effort value).
- The marker is decorative (aria-hidden); its status reaches AT via an sr-only tone word ('Error: …', 'Success: …') so colour is never the sole signal (WCAG 1.4.1). neutral/brand/muted carry no status word.
- A Timeline.Group renders an <li> heading over a nested <ol>, so AT announces a sub-list under the section header without faking flat numbering.
- Give the list an accessible name via `label`, `aria-label`, or `aria-labelledby` (dev-warns if missing). Items' actions and the collapsible-body toggle are normal focusable controls.