Timeline
Vertical or horizontal sequence of events with status-coloured markers, optional timestamps, and a continuous connecting line that runs uninterrupted between markers. Four statuses cover the standard lifecycle (pending → active → completed / error), three sizes, three line styles, and a slot for custom markers (avatars, numbers, icons).
- Mar 1, 2026Order placedConfirmation email sent.
- Mar 2, 2026ShippedCarrier handed off. Tracking #BWO-29401.
- Mar 5, 2026Out for deliveryOn the truck — expected today.
- —Delivered
Usage
import { Timeline, TimelineItem } from '@bwo-ui/react';
<Timeline>
<TimelineItem status="completed" time="Mar 1" title="Order placed">
Confirmation email sent.
</TimelineItem>
<TimelineItem status="completed" time="Mar 2" title="Shipped" />
<TimelineItem status="active" time="Mar 5" title="Out for delivery">
On the truck — expected today.
</TimelineItem>
<TimelineItem status="pending" time="—" title="Delivered" />
</Timeline>Statuses
Four TimelineItem statuses map to a colour treatment, a default marker icon, and the colour of the outgoing line:
pending— empty bordered marker, muted grey line. Hasn't started.active— accent-coloured marker with a pulsing ring. The user is here.completed— green marker, ✓ icon, green line. Done.error— red marker, × icon, red line. Failed.
- Step 1CompletedGreen marker, ✓ icon, green outgoing line.
- Step 2ActiveAccent marker, pulsing ring — the user is here.
- Step 3ErrorRed marker, × icon — something broke.
- Step 4PendingEmpty marker, muted line. Not started yet.
<TimelineItem status="completed" title="Completed" />
<TimelineItem status="active" title="Active" />
<TimelineItem status="error" title="Error" />
<TimelineItem status="pending" title="Pending" />The line that runs out of an item takes that item's status colour. So a completed-then-pending sequence renders as green-to-grey on the boundary.
Sizes
sm (10 px marker) for dense lists like settings audit logs and ambient activity feeds; md (14 px, default) for general use; lg (20 px) for hero positions like onboarding walkthroughs and big-process pages. Marker size, line thickness, and inter-item spacing all scale together.
size=sm- MonKick-off
- TueBuild
- WedShip
size=md- MonKick-off
- TueBuild
- WedShip
size=lg- MonKick-off
- TueBuild
- WedShip
<Timeline size="sm">…</Timeline>
<Timeline>…</Timeline> {/* md */}
<Timeline size="lg">…</Timeline>Connector style
solid (default) for confirmed sequences; dashed for upcoming / tentative steps (changelog drafts, soft milestones); dotted for the lightest, most ambient line.
connectorStyle=solid- MonKick-off
- TueDesign
- WedBuild
- ThuShip
connectorStyle=dashed- MonKick-off
- TueDesign
- WedBuild
- ThuShip
connectorStyle=dotted- MonKick-off
- TueDesign
- WedBuild
- ThuShip
<Timeline connectorStyle="solid">…</Timeline>
<Timeline connectorStyle="dashed">…</Timeline>
<Timeline connectorStyle="dotted">…</Timeline>Alignment
Vertical timelines default to align="right" (content on the right of the line). Switch to align="left" when the timeline sits on the right edge of a page, or when an RTL layout calls for it. Horizontal timelines default to align="bottom" (content under the line); align="top" flips that.
align="right" (default)- MonKick-off
- TueBuild
- WedShip
align="left"- MonKick-off
- TueBuild
- WedShip
<Timeline align="right">…</Timeline> {/* default */}
<Timeline align="left">…</Timeline>
<Timeline orientation="horizontal">…</Timeline> {/* align="bottom" */}
<Timeline orientation="horizontal" align="top">…</Timeline>Horizontal
For stepper-style flows where the user scans left-to-right — checkout, multi-step sign-up, shipment milestones. The container automatically overflows on small screens, keeping every step reachable via horizontal scroll.
- Mar 1Placed
- Mar 2Shipped
- Mar 5Out
- —Delivered
<Timeline orientation="horizontal">
<TimelineItem status="completed" time="Mar 1" title="Placed" />
<TimelineItem status="completed" time="Mar 2" title="Shipped" />
<TimelineItem status="active" time="Mar 5" title="Out" />
<TimelineItem status="pending" time="—" title="Delivered" />
</Timeline>Custom markers
Pass any node to marker on a TimelineItem to replace the default dot — avatars for who-did-what feeds, numbers for stepper UIs, custom icons for domain-specific events. The marker box still respects the timeline's size so the line stays aligned.
- AR2 days agoAna approved
- MSnowMihai reviewing
- ILpendingIoana to approve
<Timeline size="lg">
<TimelineItem
status="completed"
time="2 days ago"
title="Ana approved"
marker={<Avatar size="xs" fallback="AR" style={{ background: '#ff481f' }} />}
/>
<TimelineItem
status="active"
time="now"
title="Mihai reviewing"
marker={<Avatar size="xs" fallback="MS" style={{ background: '#7463ff' }} />}
/>
…
</Timeline>Recipes
Numbered stepper
Onboarding flows commonly want numbered markers — "step 1 of 4". Use the marker slot with a small bold number; let the completed status keep its default ✓ icon so finished steps stand out.
- DoneAccount details
- 2In progressProfile info
- 3Step 3Invite team
- 4Step 4Review & finish
<Timeline size="lg">
<TimelineItem status="completed" title="Account details" time="Done" />
<TimelineItem status="active" title="Profile info" time="In progress"
marker={<NumberMarker n={2} />} />
<TimelineItem status="pending" title="Invite team" time="Step 3"
marker={<NumberMarker n={3} />} />
<TimelineItem status="pending" title="Review & finish" time="Step 4"
marker={<NumberMarker n={4} />} />
</Timeline>Changelog
Release notes lay out cleanly as a timeline — the newest entry is the active node, past entries are completed, and dashed connectors signal "still in motion" for the ongoing line.
- 0.5.0 · todayNext milestoneLiveDate picker, command palette, redesigned Toast — every primitive from scratch.
- 0.4.0 · last weekSelect + MultiSelect grew
multiple+searchablemodes; AppShell got analignprop; new Rate component. - 0.3.0 · 3 weeks agoFrom-scratch rewriteDropped every
@radix-ui/*dependency. Owned primitives end-to-end. - 0.2.1Motion + UI expansionGSAP-backed motion primitives, customizable radius, docs site overhaul.
<Timeline connectorStyle="dashed">
<TimelineItem status="active" time="0.5.0 · today" title={<>Next milestone <Badge>Live</Badge></>}>
Date picker, command palette, redesigned Toast.
</TimelineItem>
<TimelineItem status="completed" time="0.4.0 · last week" title="Select + Multi">
Select grew `multiple` + `searchable` modes…
</TimelineItem>
<TimelineItem status="completed" time="0.3.0" title="From-scratch rewrite">
Dropped every `@radix-ui/*` dependency.
</TimelineItem>
</Timeline>Process / hiring funnel
Multi-stage processes (interviews, deal pipelines, approval flows) read naturally as a timeline. Mix statuses to convey where things stand without an extra status legend.
- Step 1Submit applicationStandard form — name, email, link to portfolio.
- Step 2Screening call30-minute intro with the team lead.
- Step 3Technical interviewCoding session — design + implement a component live.
- Step 4OfferCompensation, start date, paperwork.
<Timeline>
<TimelineItem status="completed" time="Step 1" title="Submit application">…</TimelineItem>
<TimelineItem status="completed" time="Step 2" title="Screening call">…</TimelineItem>
<TimelineItem status="active" time="Step 3" title="Technical interview">…</TimelineItem>
<TimelineItem status="pending" time="Step 4" title="Offer">…</TimelineItem>
</Timeline>Continuous line
The connecting line is rendered as a ::before pseudo-element on each item, positioned absolutely from the bottom of its marker through to the top of the next item's marker. There's no break at the padding boundary — what you see is one line per item, with no visible seam where one ends and the next begins. The last item auto-hides its connector; pass hideConnector on any earlier item if you need to terminate a branch mid-list.
Accessibility
- Timeline renders as an ordered list (
<ol>) of items (<li>) — screen readers announce position ("item 2 of 4"). - Status colour is reinforced by the marker shape (✓ for completed, × for error, empty for pending, accent dot for active). Colour-blind users still get the meaning from the icons and the title text.
- The active-item pulse animation is paused under
prefers-reduced-motion: reduce. - For truly dynamic timelines (a build log that ticks new entries in), wrap the list in a region with
aria-live="polite"so new items get announced as they appear. - Custom markers should carry
aria-hiddenif they duplicate information that's already in the title (e.g. an icon for "deployment" next to a title that already says "Deployed").
Timeline props
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'vertical' | 'horizontal' | 'vertical' | Layout direction. |
align | 'left' | 'right' | 'top' | 'bottom' | — | Content position relative to the line. Defaults to 'right' for vertical, 'bottom' for horizontal. |
size | 'sm' | 'md' | 'lg' | 'md' | 10 / 14 / 20 px marker. Line thickness and inter-item spacing scale together. |
connectorStyle | 'solid' | 'dashed' | 'dotted' | 'solid' | Visual style of the connecting line. |
…rest | HTMLAttributes<HTMLOListElement> | — | Native attributes are forwarded to the `<ol>` — `style`, `className`, `aria-*`. |
TimelineItem props
| Prop | Type | Default | Description |
|---|---|---|---|
status | 'pending' | 'active' | 'completed' | 'error' | 'pending' | Marker fill, default icon, and the colour of the line running out of this item. |
title | ReactNode | — | Headline — the event itself. |
time | ReactNode | — | Timestamp or supporting label rendered above the title. |
marker | ReactNode | — | Custom marker content (avatar, number, icon). Replaces the default status icon. |
hideConnector | boolean | — | Suppress the outgoing line. The last item auto-hides; only set this for unusual cases. |
…rest | HTMLAttributes<HTMLLIElement> | — | Native `<li>` attributes are forwarded. |