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).

  1. Mar 1, 2026
    Order placed
    Confirmation email sent.
  2. Mar 2, 2026
    Shipped
    Carrier handed off. Tracking #BWO-29401.
  3. Mar 5, 2026
    Out for delivery
    On the truck — expected today.
  4. 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:

  1. Step 1
    Completed
    Green marker, ✓ icon, green outgoing line.
  2. Step 2
    Active
    Accent marker, pulsing ring — the user is here.
  3. Step 3
    Error
    Red marker, × icon — something broke.
  4. Step 4
    Pending
    Empty 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
  1. Mon
    Kick-off
  2. Tue
    Build
  3. Wed
    Ship
size=md
  1. Mon
    Kick-off
  2. Tue
    Build
  3. Wed
    Ship
size=lg
  1. Mon
    Kick-off
  2. Tue
    Build
  3. Wed
    Ship
<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
  1. Mon
    Kick-off
  2. Tue
    Design
  3. Wed
    Build
  4. Thu
    Ship
connectorStyle=dashed
  1. Mon
    Kick-off
  2. Tue
    Design
  3. Wed
    Build
  4. Thu
    Ship
connectorStyle=dotted
  1. Mon
    Kick-off
  2. Tue
    Design
  3. Wed
    Build
  4. Thu
    Ship
<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)
  1. Mon
    Kick-off
  2. Tue
    Build
  3. Wed
    Ship
align="left"
  1. Mon
    Kick-off
  2. Tue
    Build
  3. Wed
    Ship
<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.

  1. Mar 1
    Placed
  2. Mar 2
    Shipped
  3. Mar 5
    Out
  4. 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.

  1. AR
    2 days ago
    Ana approved
  2. MS
    now
    Mihai reviewing
  3. IL
    pending
    Ioana 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.

  1. Done
    Account details
  2. 2
    In progress
    Profile info
  3. 3
    Step 3
    Invite team
  4. 4
    Step 4
    Review & 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.

  1. 0.5.0 · today
    Next milestoneLive
    Date picker, command palette, redesigned Toast — every primitive from scratch.
  2. 0.4.0 · last week
    Select + Multi
    Select grew multiple + searchable modes; AppShell got an align prop; new Rate component.
  3. 0.3.0 · 3 weeks ago
    From-scratch rewrite
    Dropped every @radix-ui/* dependency. Owned primitives end-to-end.
  4. 0.2.1
    Motion + UI expansion
    GSAP-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.

  1. Step 1
    Submit application
    Standard form — name, email, link to portfolio.
  2. Step 2
    Screening call
    30-minute intro with the team lead.
  3. Step 3
    Technical interview
    Coding session — design + implement a component live.
  4. Step 4
    Offer
    Compensation, 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 props

PropTypeDefaultDescription
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.
…restHTMLAttributes<HTMLOListElement>Native attributes are forwarded to the `<ol>` — `style`, `className`, `aria-*`.

TimelineItem props

PropTypeDefaultDescription
status'pending' | 'active' | 'completed' | 'error''pending'Marker fill, default icon, and the colour of the line running out of this item.
titleReactNodeHeadline — the event itself.
timeReactNodeTimestamp or supporting label rendered above the title.
markerReactNodeCustom marker content (avatar, number, icon). Replaces the default status icon.
hideConnectorbooleanSuppress the outgoing line. The last item auto-hides; only set this for unusual cases.
…restHTMLAttributes<HTMLLIElement>Native `<li>` attributes are forwarded.