Timeline
Timeline — a record of things that happened, in order: an order's history, a changelog, an activity feed, an audit trail.
Examples
-
Order placed
Three items, paid by card.
-
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.
| Token | Default |
|---|---|
| --timeline-connector-color | var(--color-border) |
| --timeline-connector-inset | var(--space-1) |
| --timeline-connector-size | 2px |
| --timeline-detail-color | var(--color-on-surface-muted) |
| --timeline-marker-bg | var(--color-surface) |
| --timeline-marker-border-color | var(--color-border-strong) |
| --timeline-marker-border-width | 2px |
| --timeline-marker-offset | calc( ( var(--timeline-time-font-size, 0.8125rem) * 1.35 - var(--timeline-marker-size, 0.875rem) ) / 2 ) |
| --timeline-marker-radius | var(--radius-full) |
| --timeline-marker-size | 0.875rem |
| --timeline-time-color | var(--color-on-surface-muted) |
| --timeline-time-font-size | var(--font-size-xs) |
| --timeline-time-font-weight | var(--font-weight-medium) |
| --timeline-time-letter-spacing | var(--letter-spacing-wide) |
| --timeline-title-color | var(--color-on-surface) |
| --timeline-title-font-weight | var(--font-weight-medium) |
| --timeline-column-gap | var(--space-3) |
| --timeline-content-gap | var(--space-1) |
| --timeline-font-family | var(--font-secondary) |
| --timeline-font-size | var(--font-size-sm) |
| --timeline-gap | var(--space-6) |
| --timeline-marker-ring-color | transparent |
| --timeline-marker-ring-size | 0 |
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
- Give every
<time>adatetimeattribute 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. - 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". - 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 isaria-hiddenbecause it has nothing to say. - 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-offseton that entry if you do. - Keep entries in chronological order in the DOM, in whichever direction the page reads. Nothing here reorders anything, and
flex-direction: reverseon a list would separate reading order from DOM order..timeline-item,.timeline-marker,.timeline-content,.timeline-time,.timeline-titleand.timeline-detailare 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