Timeline

ol.timeline[role="list"]

Timeline — a record of things that happened, in order: an order's history, a changelog, an activity feed, an audit trail.

Examples

An order’s history

View “An order’s history” as
  1. Order placed

    Three items, paid by card.

  2. Shipped

<ol class="timeline" role="list">
  <li class="timeline-item">
    <span class="timeline-marker" aria-hidden="true"></span>
    <div class="timeline-content">
      <time class="timeline-time" datetime="2026-08-08T09:12">8 August, 09:12</time>
      <p class="timeline-title">Order placed</p>
      <p class="timeline-detail">Three items, paid by card.</p>
    </div>
  </li>
  <li class="timeline-item timeline-highlight">
    <span class="timeline-marker" aria-hidden="true"></span>
    <div class="timeline-content">
      <time class="timeline-time" datetime="2026-08-09">9 August</time>
      <p class="timeline-title">Shipped</p>
    </div>
  </li>
</ol>

A timeline is a record of what happened; steps is a progress indicator through a flow you are in.

They look alike, and that difference decides almost every rule in the file. The datetime attribute is not optional — CSS cannot supply a machine-readable date.

Tokens 23

Level 2, declared on .timeline itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by timeline
TokenDefault
--timeline-connector-colorvar(--color-border)
--timeline-connector-insetvar(--space-1)
--timeline-connector-size2px
--timeline-detail-colorvar(--color-on-surface-muted)
--timeline-marker-bgvar(--color-surface)
--timeline-marker-border-colorvar(--color-border-strong)
--timeline-marker-border-width2px
--timeline-marker-offsetcalc( ( var(--timeline-time-font-size, 0.8125rem) * 1.35 - var(--timeline-marker-size, 0.875rem) ) / 2 )
--timeline-marker-radiusvar(--radius-full)
--timeline-marker-size0.875rem
--timeline-time-colorvar(--color-on-surface-muted)
--timeline-time-font-sizevar(--font-size-xs)
--timeline-time-font-weightvar(--font-weight-medium)
--timeline-time-letter-spacingvar(--letter-spacing-wide)
--timeline-title-colorvar(--color-on-surface)
--timeline-title-font-weightvar(--font-weight-medium)
--timeline-column-gapvar(--space-3)
--timeline-content-gapvar(--space-1)
--timeline-font-familyvar(--font-secondary)
--timeline-font-sizevar(--font-size-sm)
--timeline-gapvar(--space-6)
--timeline-marker-ring-colortransparent
--timeline-marker-ring-size0

Variants and states

Variants

  • .timeline-lg
  • .timeline-sm

Inside it

  • .timeline-link
  • .timeline-error
  • .timeline-item
  • .timeline-marker
  • .timeline-content
  • .timeline-detail
  • .timeline-highlight
  • .timeline-time
  • .timeline-title

State it reads

Read from the platform, never mirrored into a class that could disagree with it.

  • :focus
  • :hover

Before you ship it

What you have to do 5 requirements

  1. Give every <time> a datetime attribute in a valid format (2026-08-08, 2026-08-08T09:12, 2026-08). The visible text can be anything readable; the attribute is what a machine gets, and CSS cannot supply it. <time> without it is a <span> with extra steps.
  2. Keep role="list" on the <ol>. The reset strips markers from a classed list, and that is what makes VoiceOver stop announcing "list, 6 items".
  3. Say what a variant means in text. The diamond and the halo are pixels and announce nothing: write "Failed" in the title, or add <span class="sr-only">Failed</span>. The marker is aria-hidden because it has nothing to say.
  4. Put the <time> first inside .timeline-content. The marker is aligned to the middle of the date's line box, so an entry that leads with a larger line — a title, a heading — leaves the dot sitting slightly high. Reorder by all means, and set --timeline-marker-offset on that entry if you do.
  5. Keep entries in chronological order in the DOM, in whichever direction the page reads. Nothing here reorders anything, and flex-direction: reverse on a list would separate reading order from DOM order. .timeline-item, .timeline-marker, .timeline-content, .timeline-time, .timeline-title and .timeline-detail are only meaningful inside .timeline; every property that reads a component token carries a fallback, so one used loose degrades to the page defaults rather than to an undefined variable.

src/css/components/timeline.css · npx mostlycss add timeline