Timeline
Events in order, each with its time: a history of what happened, or the steps of something still under way.
cennge-ui releases
- v0.3.1README maps set in New York
- v0.3.0Charts, ThemeScript, ThemeSwitch
- v0.2.0Maps on MapLibre GL
- v0.1.1Date limits take null
- v0.1.0First release
payload-cache v1.10.0
- Build1m 12s
- Tests3m 40s
- PreviewLiveWaits for someone to promote it
- Production, upcoming
- Without activeIndex, a timeline is a history: every item is done and takes the colour.
- Promote the deploy: activeIndex moves on, Production takes the colour and becomes the current step.
- Items past activeIndex stay muted, and screen readers hear them as upcoming.
Field marks
- Import
import { Timeline, TimelineItem } from "cennge-ui";- Size
- 1.7 KB gzip for this import, its dependencies aside
- Pattern
- Native
<ol>; a time with adateTimeis a<time>; the current step isaria-current="step" - State
- None to hold:
activeIndexsays how far it has got - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Horizontal
- Scrolls inside itself when too wide, and takes Tab so the arrows scroll it
- Tests
- Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: timeline
- Source
- src/components/timeline
When to use#
Use a Timeline for events in the order they happened: releases, deploys, the life of an issue. With activeIndex it tracks something under way instead, colouring the steps that are done and muting those to come.
Each TimelineItem has a title, an optional time, and anything you put inside it as the description. The list is an <ol> and every item an <li>, so the order is in the markup, not only on the screen.
Similar#
- Stepper is a form or a task in steps that people move through and click; a Timeline only shows where things stand.
- Table suits events you compare by column or sort; a Timeline reads top to bottom.
Tracking progress#
activeIndex is the index of the current item. Items up to it are done and take the color; the current one is aria-current="step"; later ones are muted, and screen readers hear ", upcoming" after their title. lineVariant="dashed" draws the line to the next item dashed, here for a release that has no date yet.
- ReportedCached pages keep their old TTL after a config change
- ReproducedA failing test on the fix/ttl branch
- Fix mergedWaiting for the next release
- Released, upcoming
import { Timeline, TimelineItem } from "cennge-ui";
export default function IssueProgress() {
return (
<Timeline aria-label="Progress of the payload-cache issue" activeIndex={2} className="w-full max-w-80">
<TimelineItem title="Reported" time="Nov 2" dateTime="2026-11-02">
Cached pages keep their old TTL after a config change
</TimelineItem>
<TimelineItem title="Reproduced" time="Nov 3" dateTime="2026-11-03">
A failing test on the fix/ttl branch
</TimelineItem>
<TimelineItem title="Fix merged" time="Nov 5" dateTime="2026-11-05" lineVariant="dashed">
Waiting for the next release
</TimelineItem>
<TimelineItem title="Released" />
</Timeline>
);
}Colours, icons and plans#
color on an item overrides the timeline's, so a failed deploy can be red in a history of green ones. bullet puts an icon in the dot. Give an item a dateTime beside its time and the time becomes a <time> element that machines can read; text such as "Next week" without one stays a <span>.
- v1.11.0 plannedNext week
- v1.10.0 deployedProduction, in 84 seconds
- Deploy failedTypecheck exited with code 2
- v1.9.2 deployedProduction, in 79 seconds
import { Timeline, TimelineItem } from "cennge-ui";
function CheckIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" aria-hidden="true">
<path d="M5 12.5l4.5 4.5L19 7.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function CrossIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" aria-hidden="true">
<path d="M7 7l10 10M17 7 7 17" strokeLinecap="round" />
</svg>
);
}
export default function DeployHistory() {
return (
<Timeline aria-label="Deploys of payload-cache" className="w-full max-w-80">
<TimelineItem title="v1.11.0 planned" time="Next week" color="default" lineVariant="dashed" />
<TimelineItem title="v1.10.0 deployed" time="Nov 6, 09:12" dateTime="2026-11-06T09:12Z" color="success" bullet={<CheckIcon />}>
Production, in 84 seconds
</TimelineItem>
<TimelineItem title="Deploy failed" time="Nov 5, 17:40" dateTime="2026-11-05T17:40Z" color="danger" bullet={<CrossIcon />}>
Typecheck exited with code 2
</TimelineItem>
<TimelineItem title="v1.9.2 deployed" time="Oct 29, 13:27" dateTime="2026-10-29T13:27Z" color="success" bullet={<CheckIcon />}>
Production, in 79 seconds
</TimelineItem>
</Timeline>
);
}Horizontal#
orientation="horizontal" lays the items out in a row, each at least 11rem wide. When they do not fit, as on a phone, the row scrolls inside itself rather than widening the page, and it is in the tab order so the arrow keys can scroll it.
import { Timeline, TimelineItem } from "cennge-ui";
export default function ReleaseFlow() {
return (
<Timeline aria-label="How cennge-ui 0.3.1 was released" orientation="horizontal" className="w-full">
<TimelineItem title="Version PR" time="Oct 31" dateTime="2026-10-31T23:27+02:00">
Opened by changesets/action
</TimelineItem>
<TimelineItem title="Merged" time="Nov 1" dateTime="2026-11-01T10:01+02:00">
Pull request #4
</TimelineItem>
<TimelineItem title="Published" time="Nov 1" dateTime="2026-11-01T10:40+02:00">
cennge-ui@0.3.1 on npm
</TimelineItem>
</Timeline>
);
}Accessibility#
- Name the timeline with
aria-labeloraria-labelledby; it is a list, and screen readers say how many items it holds. - Bullets and lines are decoration, hidden from screen readers. The state is in the text:
aria-current="step"on the current item and ", upcoming" after the titles to come.upcomingLabelchanges that word, e.g. for another language. - A horizontal timeline has
tabIndex={0}whether or not it overflows, so it is always one Tab stop.
Props#
Timeline#
Also takes every attribute of <olist>.
TimelineItem#
Also takes every attribute of <li>, except title, color, which mean something else here.