Timeline

A composable timeline — a real ordered list with a marker rail, six semantic statuses, and event content you compose yourself from cards, chips, avatars, and semantic HTML.

Usage

Pull request #4812

Timeline component and docs page

  1. Pull request opened#4812

    Priya Raman opened the pull request against release/1.4.

  2. Review requested2 reviewers

    Priya Raman requested review from platform-core.

  3. Changes requestedblocking

    Tomas Brandt flagged the connector bleed in timeline.css.

  4. Checks green812 tests

    CI passed on the third run after a flaky retry.

  5. Approved2 of 2

    Aiyana Cole signed off alongside Dan Whitfield.

  6. Merged to mainsquash

    Dan Whitfield merged and deleted the branch.

Timeline is for read-only chronology: activity feeds, audit trails, incident history, release logs, roadmaps. Anything where the order is the meaning and the reader is looking back at what happened.

It deliberately has no notion of a current step you can advance, no completion state, and nothing to click. Events are content, not controls. If the user needs to move through a known sequence — a checkout, an onboarding flow, a multi-step form — a timeline is the wrong shape; that job wants a stepper, which this library does not yet ship.

The API is composable rather than a single component with props, which is the same exception the Legend takes. An event carries a card, a chip, an avatar or prose, and props cannot express that. Children can.

Anatomy

Import the Timeline component and access all parts using dot notation.

import {Timeline} from "@blakeui/pro-react";

<Timeline>
  <Timeline.Item>
    <Timeline.Rail>
      <Timeline.Marker />
      <Timeline.Connector />
    </Timeline.Rail>
    <Timeline.Content />
  </Timeline.Item>
</Timeline>;

Timeline.Rail, Timeline.Marker and Timeline.Connector are all supplied automatically when you leave them out, so an item needs only its Timeline.Content — write them explicitly when you want to give the marker children or force a connector.

The component owns the chronology layout and nothing else. It renders an <ol> of <li> and gives each item a rail; everything inside Timeline.Content is yours, and keeps its own semantics — headings stay headings, <time> stays <time>, links stay focusable. Use real HTML and real components in there rather than reaching for props that do not exist.

Centered Milestones

axis="center" runs the rail down the middle, which is the only axis on which placement means anything — an edge rail has one usable side, so every item resolves to side="end" there. placement="alternate" then flips content from side to side, and itemAlign="center" puts each marker at the middle of its own block so the alternation reads as one spine.

2026 roadmap
  1. Shipped

    Kickoff

    Scope agreed with the platform team and the two design partners who asked for it first.

    14 requirements · 3 teams

  2. Shipped

    Layout spike

    Two-week spike on the rail geometry. The three-row grid came out of this.

    6 prototypes · 1 kept

  3. Shipped

    Internal alpha

    Internal alpha behind a flag, seeded with the activity feed from the release dashboard.

    38 internal users

  4. Shipped

    Accessibility audit

    Accessibility pass. The tree was rebuilt around a real ordered list after the first audit.

    9 findings · 9 closed

  5. Current

    Public beta

    Opened to 40 design-partner accounts with the composable API frozen.

    40 accounts · 1.2k items rendered

  6. Planned

    General availability

    Billing, the migration guide, and the codemod off the old activity list.

    3 blockers open

  7. Planned

    Virtualised feeds

    Virtualised rendering for feeds past a thousand events, sharing the data-grid virtualiser.

    Target: 10k items

Studio Review

  1. M
    Checkout — payment stepNeeds workMarguerite Okonjo · Today, 09:20
    Mobile checkout screen under review: an order summary listing two items totalling $148.00, a payment method picker with Credit or Debit Card selected over Digital Wallet, and a Pay $148.00 button

    The card total sits below the fold at 375px, so the pay button is the first thing a thumb reaches. Moving the summary above the method picker fixes it without a new screen.

    MobileCheckout2 blockers
    Open the annotated frame
  2. T
    Dashboard — first-run empty stateTomas Brandt · Today, 08:05
  3. A
    Reports — export drawerApprovedAiyana Cole · Yesterday, 17:42
  4. K
    Settings — billing plan pickerKofi Mensah · Yesterday, 15:10
  5. I
    Onboarding — workspace inviteApprovedInes Delacroix · Yesterday, 11:33
  6. R
    Search — result groupingRina Takahashi · Mon, 16:58

Compact Log

  1. Promoted to production

    Build 4812 promoted after the canary held for an hour.

  2. Rotated the staging signing key
  3. Stale token revoked

    d.whitfield revoked a token last used 94 days ago.

  4. Reconciled 1,284 invoices
  5. Access granted

    m.okonjo granted read access to analytics-ro.

  6. Nightly snapshot uploaded
  7. Pool sizing changed

    Connection pool ceiling raised from 40 to 120.

  8. Maintenance window closed
  9. Workers restarted

    Two workers restarted after an OOM in the export queue.

  10. Retention sweep completed

Incident Response

Incident INC-2291

Checkout error rate above 5%

  1. M
    Marguerite Okonjoposted the write-up

    The pool ceiling had been at 40 since the eu-west-1 cutover in May. Under the promo burst it saturated in ninety seconds and checkout queued behind it. Raising the ceiling cleared it; the sizing now moves into the deploy check so it cannot drift again.

    inc-2291-postmortem.pdf · 248 KBpool-saturation.png · 1.1 MB
    Open the latency dashboard
  2. P
    Priya Ramanopened the incident
  3. D
    Dan Whitfieldpaged the database on-call
  4. K
    Kofi Mensahshifted traffic away from eu-west-1
  5. A
    Aiyana Coleraised the connection pool ceiling
  6. R
    Rina Takahashiconfirmed the error rate under 0.1%
  7. I
    Ines Delacroixclosed the incident

Version History

Version history

  1. v1.4.0Latest

    Timeline ships. Legend gains a value suffix and the odometer keeps its slot on shorter values.

    PPriya Raman · 24 Jul 2026
  2. v1.3.0

    Overlay easing moved off the plain-ease alias across eight components.

    DDan Whitfield · 11 Jul 2026
  3. v1.2.0

    Table header and row-pill radius normalised to 16px so primary and secondary match.

    AAiyana Cole · 29 Jun 2026
  4. v1.1.0Breaking

    Charts moved to visx. Recharts removed from all twelve engines.

    KKofi Mensah · 2 Jun 2026
  5. v1.0.4

    Data grid gains virtualised rows and a run-aware multi-select.

    IInes Delacroix · 18 May 2026
  6. v1.0.2

    Item card and item card group settled as bordered-flat surfaces.

    RRina Takahashi · 3 May 2026
  7. v1.0.0Initial

    First public release: twelve charts, six data-display components, one theme.

    TTomas Brandt · 15 Apr 2026

Repository Activity

July 2026
  1. Pushed to two repositories
    lumenforge/interface
    126 commits
    lumenforge/tokens
    18 commits
  2. Opened a pull request

    Pin the rail and the content to grid row 1

    Auto-placement cannot walk backwards, so a start-side item dropped its content into a second row and left the rail at its own height.

    +128−346
  3. Triaged the backlog
    lumenforge/interface
    3 issues
    timelinea11yneeds-design
  4. Pushed to two repositories
    lumenforge/docs
    41 commits
    lumenforge/examples
    9 commits
  5. Reviewed the rail geometry
    lumenforge/interface#812
    14 comments
  6. Cut a release
    lumenforge/interface
    tagged v1.4.0
    release1.4.0
  7. Pushed to three repositories
    lumenforge/interface
    58 commits
    lumenforge/tokens
    6 commits
    lumenforge/docs
    23 commits

Split Content

On-call rotation

Tuesday, 27 July

  1. Handover

    Priya Raman

    Shift start

    Two pages carried over from the Dublin desk, both on the export queue.

    Open pages
    2
    Ack time
    4m 12s
  2. Alert

    Dan Whitfield

    Latency alert cleared

    Latency on the search cluster crossed the warning threshold and cleared without a page.

    Peak p99
    1.8s
    Duration
    9m
  3. Deploy

    Aiyana Cole

    Release promoted

    Release 4812 promoted after the canary held a full hour at 10% of traffic.

    Canary
    60m
    Error rate
    0.02%
  4. Maintenance

    Kofi Mensah

    Replica rebuilt

    Replica rebuilt from the 04:00 snapshot; replication lag back inside a second.

    Lag before
    42s
    Lag after
    0.6s
  5. Alert

    Rina Takahashi

    Workers restarted

    Export workers restarted twice after an OOM traced to a single 900MB report.

    Restarts
    2
    Queue depth
    318
  6. Handover

    Ines Delacroix

    Shift end

    Handed to Singapore with one follow-up filed against the export sizing.

    Open pages
    1
    Follow-ups
    1

CSS Classes

Base Classes

  • .timeline — The root <ol>. display: flex in a column with row-gap: var(--timeline-gap), min-width: 0, and list styling stripped. The vertical rhythm is this gap rather than padding on the item, which is what lets a centred marker sit at the true middle of its own content; the two lines bridge the gap by bleeding into it with a negative margin.
  • .timeline--axis-start — Two columns, auto minmax(0, 1fr): rail then content. The default.
  • .timeline--axis-center — Three columns, minmax(0, 1fr) auto minmax(0, 1fr): content, rail, content. Both outer columns are minmax(0, …), so long content wraps instead of pushing the rail off centre.
  • .timeline--size-sm / .timeline--size-md / .timeline--size-lg — Set the four size variables together: a 12 / 16 / 20px marker, a 1 / 2 / 2px line, a 10 / 12 / 16px rail gap, and 13 / 14 / 15px content.
  • .timeline--density-compact / .timeline--density-comfortable — Set --timeline-gap to 12px or 24px. Nothing else moves.

Element Classes

  • .timeline__item — The <li>, display: grid with column-gap: var(--timeline-rail-gap). Carries data-status, data-side and data-align, plus aria-current="true" when the status is current.
  • .timeline__rail — The marker-and-connector column, itself a three-row grid — lead line, marker, connector — justify-items: center and align-self: stretch. At data-align="start" the rows are 0 auto 1fr, so the marker tops out and no line is drawn above it; at data-align="center" they are 1fr auto 1fr and a ::before lead line fills row 1 on every item but the first.
  • .timeline__marker — The dot. var(--timeline-marker-size) square, fully rounded, with border: 1px solid var(--timeline-marker-ring), background-color: var(--timeline-marker-fill) and color: var(--timeline-marker-ink). Bordered-flat — a ring and a fill, never a shadow. A marker with children (:not(:empty)) grows to --timeline-marker-size + 8px on both axes and takes 2px of padding, and a descendant svg is forced to 12px square.
  • .timeline__connector — The line to the next event. var(--timeline-line-width) wide, background-color: var(--separator), filling row 3 of the rail and bleeding a full --timeline-gap past the item so it meets the next marker. On the last item — where it exists only because force asked for it — it becomes a 24px stub with no bleed, and :has() grows the item to hold it so the ink never leaves the <ol>'s box.
  • .timeline__content — The event. min-width: 0, font-size: var(--timeline-font-size), line-height: 1.5, color: var(--foreground). Under a centred axis its data-side picks column 1 or 3 and hugs it to the rail with justify-self, which aligns the block without re-aligning your text.

Status Classes

Status is a data-status attribute rather than a class, set on both the item and the marker. The marker's own value wins by proximity, so <Timeline.Marker status="danger" /> inside a default item needs no extra specificity. Each value sets the same three custom properties:

  • [data-status="default"]--default fill, --muted ring, --default-foreground ink. A solid neutral dot.
  • [data-status="current"]--accent-soft fill, --accent-soft-foreground ring and ink, and a 2px ring instead of 1px so the live event is findable at a glance. This is the status that carries aria-current.
  • [data-status="success"]--success-soft fill, --success-soft-foreground ring and ink.
  • [data-status="warning"]--warning-soft fill, --warning-soft-foreground ring and ink.
  • [data-status="danger"]--danger-soft fill, --danger-soft-foreground ring and ink. The raw --danger is never painted as a fill or as text: it is identical in both themes and measures 1.89:1 on the dark surface.
  • [data-status="muted"]--default-soft fill, --muted ring, --default-foreground ink. Reads as hollow beside default's filled dot, so de-emphasis comes from the fill rather than from dimming the glyph.

Every ring clears the 3:1 non-text contrast floor in both themes. Marker ink clears 4.5:1 throughout in light; in dark, current and danger sit at ~3.4:1 against their own tint, so prefer an icon or a short glyph over a text label in those two, or leave the marker empty.

CSS Variables

All six are declared on .timeline and inherited by every part, so overriding one on the root retunes the whole component.

  • --timeline-marker-size — Marker diameter. 16px at md.
  • --timeline-line-width — Connector and lead-line thickness. 2px at md.
  • --timeline-rail-gap — Horizontal space between the rail and the content. 12px at md.
  • --timeline-font-size — Content type size. 0.875rem at md.
  • --timeline-gap — Vertical space between items. 24px at comfortable.
  • --timeline-marker-nudge — Derived, not authored: how far a start-aligned marker drops to sit on the first line's optical centre, (--timeline-font-size × 1.5 − --timeline-marker-size) ÷ 2. The connector adds it to its own bleed so the stroke still reaches the next marker.

Motion

Items animate in on mount — opacity plus an 8px rise over 280ms on var(--ease-out), staggered 50ms per item. The animation is motion/react rather than a CSS transition, so the reduced-motion opt-out lives in JS and covers both halves: prefers-reduced-motion: reduce and an explicit [data-reduce-motion="true"] on the component or any ancestor. Under either, the stagger is dropped and the rise is snapped out in a single frame while the fade plays at full length — gentler, never frozen.

API Reference

Timeline

The root. Renders an <ol>, holds the layout contract, and hands each item its index. Also accepts every native <ol> attribute.

PropTypeDefaultDescription
childrenReactNodeTimeline.Item children. Required.
axis"start" | "center""start"Where the rail runs — along the inline-start edge, or down the middle. Only a centred rail has two usable sides.
placement"start" | "end" | "alternate""end"Which side of the rail content sits on. alternate flips it per item. Only meaningful with axis="center" — under an edge rail every item resolves to "end".
size"sm" | "md" | "lg""md"Marker diameter, line weight, rail gap, and content type scale, moved together.
density"compact" | "comfortable""comfortable"Vertical rhythm between items — --timeline-gap alone. Nothing else moves.
itemAlign"start" | "center""start"Default vertical alignment of content against its marker. center also draws a lead line above each marker, so a centred rail stays unbroken.

Timeline.Item

One event — an <li>. Also accepts every native <li> attribute.

PropTypeDefaultDescription
childrenReactNodeItem content — a Timeline.Rail and one or more Timeline.Content. A rail is supplied when omitted. Required.
status"default" | "current" | "success" | "warning" | "danger" | "muted""default"Semantic tone, painted by the marker. "current" also marks the item aria-current="true" — the others are visual only.
align"start" | "center"itemAlignOverrides the root's alignment for this item.
side"start" | "end"Overrides the side resolved from placement for this item. Ignored under axis="start".

Timeline.Rail

The marker-and-connector column. Rendered automatically when an item omits it. Also accepts every native <div> attribute.

PropTypeDefaultDescription
childrenReactNodeRail content. A Timeline.Marker and a Timeline.Connector are each supplied when absent, so passing only one keeps the other's default.
classNamestringClass name for the rail.

Timeline.Marker

The dot on the rail. Also accepts every native <span> attribute.

PropTypeDefaultDescription
childrenReactNodeMarker content — a number, a glyph, an avatar. An empty marker is decoration and is aria-hidden; one with children is content and stays in the accessibility tree.
status"default" | "current" | "success" | "warning" | "danger" | "muted"the item's statusOverrides the item's status for this marker alone.
classNamestringClass name for the marker.

Timeline.Connector

The line to the next event. Always decorative, so always aria-hidden. Also accepts every native <span> attribute.

PropTypeDefaultDescription
forcebooleanfalseDraw the connector on the last item too, as a trailing stub — "this continues past here". Without it the last item renders no connector at all, because there is no next event to point at.
classNamestringClass name for the connector.

Timeline.Content

The event itself — a plain <div>, so whatever you put inside keeps its own semantics. Also accepts every native <div> attribute.

PropTypeDefaultDescription
childrenReactNodeThe event's content. Required.
side"start" | "end"the item's resolved sideOverrides the side for this block alone. Give one item two Timeline.Content children with opposite sides to split an event either side of a centred rail.
classNamestringClass name for the content.

useTimelineItem

Reads the current item's resolved state. Only valid inside a Timeline.Item — use it to build a custom part alongside the built-in ones.

import {useTimelineItem} from "@blakeui/pro-react";

function StepNumber() {
  const {index, isLast} = useTimelineItem();
  return <span>{isLast ? "Final" : `Step ${index + 1}`}</span>;
}
PropertyTypeDefaultDescription
align"start" | "center"Resolved alignment — the item's align, else the root's itemAlign.
indexnumberZero-based position among the root's children.
isLastbooleanTrue for the final item — this is what auto-hides its connector.
side"start" | "end"Resolved side — the item's side, else placement read against index and the axis.
status"default" | "current" | "success" | "warning" | "danger" | "muted"The item's semantic tone.

On this page