Timeline
A rail of dated events, each with a marker in its tone. It renders on the server and mirrors by itself in right-to-left layouts.
server - 0 KB
Timeline
server - 0 KBA rail of dated events, each with a marker in its tone. It renders on the server and mirrors by itself in right-to-left layouts.
- DoneProject created
- DoneFirst deploy
- CurrentIn reviewToday
- Not startedLaunchNot scheduled
The code
timeline.tsx
import { Timeline } from 'topclassui/timeline'
const EVENTS = [
{ id: 'created', title: 'Project created', time: <time dateTime="2026-10-02T09:12">2 October, 09:12</time>, status: 'done' },
{ id: 'deployed', title: 'First deploy', time: <time dateTime="2026-10-04T16:40">4 October, 16:40</time>, status: 'done' },
{ id: 'review', title: 'In review', time: 'Today', status: 'current' },
{ id: 'launch', title: 'Launch', time: 'Not scheduled', status: 'todo' },
] as const
/** The drawn rail: done filled in success, the current step a dot in a ring, and a hollow todo.
* A `<time>` gives a date a machine can read; each status is also said in hidden text. */
export default function Demo() {
return <Timeline items={EVENTS} getKey={(event) => event.id} />
}
An item's icon is an element, such as <Svg icon={check} size="xs" />, not an icon name: no icon loader ships.
Every state
6 states- DoneDoneA filled dot in success
- CurrentCurrentA dot in a ring, read as the current step
- Not startedTo doA hollow dot
- A tone of its ownNo status, the danger tone
- DoneAn iconTakes the tone of its status
- Not startedAn icon to do
Import from the package
topclassui/timeline
import { Timeline } from 'topclassui/timeline'Install once:npm i topclassui@beta zod
Examples
- An item is
{ title, time?, status?, tone?, icon? }, plus any field of your own, such as anidforgetKey. - Pass a
<time dateTime="...">astimefor a date a machine can read; it shows muted under the title. - A
doneitem is a filled dot,currenta dot in a ring andtodoa hollow dot. With no tone,doneis success and the rest are primary; atodomarker stays hollow whatever the tone. - Timeline renders the
itemsit is given, in that order: it never sorts, fetches or pages. - No items renders
emptyFallbackon its own, or nothing without one.
basic
- DoneProject created
- DoneFirst deploy
- CurrentIn reviewToday
- Not startedLaunchNot scheduled
basic.tsx
import { Timeline } from 'topclassui/timeline'
const EVENTS = [
{ id: 'created', title: 'Project created', time: <time dateTime="2026-10-02T09:12">2 October, 09:12</time>, status: 'done' },
{ id: 'deployed', title: 'First deploy', time: <time dateTime="2026-10-04T16:40">4 October, 16:40</time>, status: 'done' },
{ id: 'review', title: 'In review', time: 'Today', status: 'current' },
{ id: 'launch', title: 'Launch', time: 'Not scheduled', status: 'todo' },
] as const
/** The drawn rail: done filled in success, the current step a dot in a ring, and a hollow todo.
* A `<time>` gives a date a machine can read; each status is also said in hidden text. */
export default function Demo() {
return <Timeline items={EVENTS} getKey={(event) => event.id} />
}
tones
- PrimaryA new version is ready.
- NeutralNothing has changed since Monday.
- SuccessThe invoice was paid.
- WarningThe trial ends in three days.
- DangerThe last deploy failed.
- InfoMaintenance starts at 22:00.
tones.tsx
import { Timeline } from 'topclassui/timeline'
const EVENTS = [
{ id: 'primary', tone: 'primary', title: 'Primary', time: 'A new version is ready.' },
{ id: 'neutral', tone: 'neutral', title: 'Neutral', time: 'Nothing has changed since Monday.' },
{ id: 'success', tone: 'success', title: 'Success', time: 'The invoice was paid.' },
{ id: 'warning', tone: 'warning', title: 'Warning', time: 'The trial ends in three days.' },
{ id: 'danger', tone: 'danger', title: 'Danger', time: 'The last deploy failed.' },
{ id: 'info', tone: 'info', title: 'Info', time: 'Maintenance starts at 22:00.' },
] as const
/** Every tone as a marker, so axe measures all six in each mode. */
export default function Demo() {
return <Timeline items={EVENTS} getKey={(event) => event.id} />
}
icons
- Invoice created09:12
- Payment received11:30
- Receipt delayed11:31
- Not startedReceipt sent
icons.tsx
import { check } from 'topclassui/icons/check'
import { clock } from 'topclassui/icons/clock'
import { plus } from 'topclassui/icons/plus'
import { warning } from 'topclassui/icons/warning'
import { Svg } from 'topclassui/svg'
import { Timeline } from 'topclassui/timeline'
const EVENTS = [
{ id: 'created', icon: <Svg icon={plus} size="xs" />, title: 'Invoice created', time: '09:12' },
{ id: 'paid', icon: <Svg icon={check} size="xs" />, tone: 'success', title: 'Payment received', time: '11:30' },
{ id: 'late', icon: <Svg icon={warning} size="xs" />, tone: 'warning', title: 'Receipt delayed', time: '11:31' },
{ id: 'sent', icon: <Svg icon={clock} size="xs" />, status: 'todo', title: 'Receipt sent' },
] as const
/** An icon stands in for the dot and takes the tone; the last item has no time. */
export default function Demo() {
return <Timeline items={EVENTS} getKey={(event) => event.id} />
}
long-text
- The deploy failed while uploading the static assets to the edge networkFriday 9 October 2026 at 16:40, Central European Summer Time
- https://example.com/projects/topclassui/deployments/2026-10-09/logs/build-step-four-of-six.txtKept for thirty days
- Not startedRetry
long-text.tsx
import { Timeline } from 'topclassui/timeline'
const LOG_URL = 'https://example.com/projects/topclassui/deployments/2026-10-09/logs/build-step-four-of-six.txt'
const EVENTS = [
{
id: 'failed',
tone: 'danger',
title: 'The deploy failed while uploading the static assets to the edge network',
time: 'Friday 9 October 2026 at 16:40, Central European Summer Time',
},
{ id: 'log', title: LOG_URL, time: 'Kept for thirty days' },
{ id: 'retry', status: 'todo', title: 'Retry' },
] as const
/** A narrow box: a long title wraps, the unbroken URL breaks anywhere, and the connector grows with both. */
export default function Demo() {
return (
<div className="w-64">
<Timeline items={EVENTS} getKey={(event) => event.id} />
</div>
)
}
empty
No activity yet.
empty.tsx
import { Timeline, type TimelineItem } from 'topclassui/timeline'
const NONE: readonly (TimelineItem & { id: string })[] = []
/** No items: the fallback takes the place of the list, and without one nothing renders. */
export default function Demo() {
return (
<Timeline
items={NONE}
getKey={(event) => event.id}
emptyFallback={<p className="text-sm text-muted">No activity yet.</p>}
/>
)
}
In Arabic, right to left
- مكتملأُنشئ المشروع٢ أكتوبر، ٠٩:١٢
- مكتملوصلت الدفعة٤ أكتوبر، ١٦:٤٠
- الحاليقيد المراجعةاليوم
- لم يبدأالإطلاقلم يُحدَّد موعده
rtl.tsx
import { check } from 'topclassui/icons/check'
import { ar } from 'topclassui/labels/ar'
import { Svg } from 'topclassui/svg'
import { Timeline } from 'topclassui/timeline'
const EVENTS = [
{ id: 'created', title: 'أُنشئ المشروع', time: '٢ أكتوبر، ٠٩:١٢', status: 'done' },
{ id: 'paid', icon: <Svg icon={check} size="xs" />, title: 'وصلت الدفعة', time: '٤ أكتوبر، ١٦:٤٠', status: 'done' },
{ id: 'review', title: 'قيد المراجعة', time: 'اليوم', status: 'current' },
{ id: 'launch', title: 'الإطلاق', time: 'لم يُحدَّد موعده', status: 'todo' },
] as const
/** Right to left: the rail sits at the inline start by itself, with no direction prop.
* The Arabic labels say each status in the language of the page. */
export default function Demo() {
return (
<div dir="rtl" lang="ar">
<Timeline items={EVENTS} getKey={(event) => event.id} labels={ar} />
</div>
)
}
Props
Keyboard and accessibility
Nothing to operate: it is not interactive on its own.
- An ordered list, with
role="list"written so Safari with VoiceOver keeps it. - The marker is a shape and a colour, so each status is also said in hidden words from
labels; the current item carriesaria-current="step". - Nothing in it takes focus: it is page content, not a control.
Right to left
The rail sits at the inline start, on the right in Arabic, with no direction prop. Pass labels for the status words in Arabic.
Server and client
Renders on the server and ships no JavaScript.
Files: index.tsx.
On the chip: server - 0 KB.