Skip to content

Timeline

A read-only chronology — a toned-marker rail of ordered events for an activity feed, audit log, order history, or changelog.

Stablev0.4.2added in v0.1.0@garn/ui/timeline
On 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.

terminal
npx garn-ui add timeline
First time? Set up garn in your project
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

SelectorState
[data-state=current]current
[data-state=loading]loading
[data-dense]dense
PropertyTokenTier
gap--garn-gap-stacksemantic · air
markerFill--garn-badge-neutral-boldbrandablecomponent

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

Do
  • 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 tone for 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); use loading for the live pending tail.
  • Order items the way they should read (the component preserves the consumer's array order — no auto-sort).
Don't
  • 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 collapsible for long audit payloads).
  • Write timestamps human-readable but always pass the ISO dateTime for 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

warn

Give the Timeline an accessible name via `label`, `aria-label`, or `aria-labelledby`.

when a Timeline has no `label`, `aria-label`, or `aria-labelledby`

A1.3.1Info and RelationshipsA1.4.1Use of ColorAA1.4.3Contrast (Minimum)A2.1.1KeyboardA4.1.2Name, Role, Value
  • 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.