Data Display
Timeline
An activity feed or an order's history: events in order, each on a rail with a dot or an icon, joined by the line that makes them read as one sequence rather than a list of unrelated rows.
import { Timeline } from 'omaris' Examples
Basic
An order's history. Times are dates, written the way a person says them, with the exact moment in the tooltip; pending is the step that has not happened yet — its dot pulses and the line into it is dashed.
<script lang="ts"> import { Timeline, TimelineItem } from 'omaris'; const now = Date.now(); const MINUTE = 60_000; const HOUR = 60 * MINUTE;</script><Timeline label="Order #4021" class="w-96 max-w-full"> <TimelineItem title="Order placed" time={now - 26 * HOUR} tone="neutral" /> <TimelineItem title="Payment confirmed" description="Visa ending 4242" time={now - 26 * HOUR + 2 * MINUTE} tone="neutral" /> <TimelineItem title="Shipped from Erbil" description="Aramex AX-99120-IQ" time={now - 5 * HOUR} /> <TimelineItem title="Out for delivery" time={now - 14 * MINUTE} pending /></Timeline> Grouped
A feed grouped by day is one Timeline per group, each with a heading, which also names its list for a screen reader. compact is the dense variant for a feed rather than a history.
Today
Yesterday
<script lang="ts"> import { Timeline, TimelineItem } from 'omaris'; const now = Date.now(); const MINUTE = 60_000; const HOUR = 60 * MINUTE; const GROUPS = [ { heading: 'Today', events: [ { title: 'Ada merged #1042', at: now - 25 * MINUTE, tone: 'primary' }, { title: 'Grace commented on #1040', at: now - 2 * HOUR, tone: 'neutral' } ] }, { heading: 'Yesterday', events: [ { title: 'Build failed on main', at: now - 30 * HOUR, tone: 'destructive' }, { title: 'Release 0.9.2 published', at: now - 33 * HOUR, tone: 'success' } ] } ] as const;</script><div class="flex w-96 max-w-full flex-col gap-6"> {#each GROUPS as group (group.heading)} <Timeline heading={group.heading} variant="compact"> {#each group.events as event (event.title)} <TimelineItem title={event.title} time={event.at} tone={event.tone} /> {/each} </Timeline> {/each}</div> Icons and content
An icon puts the event in a tonal circle instead of a dot; children carry whatever the event needs underneath — a comment, a set of labels.
<script lang="ts"> import { Avatar, Chip, Text, Timeline, TimelineItem } from 'omaris'; const now = Date.now(); const MINUTE = 60_000; const HOUR = 60 * MINUTE;</script><Timeline label="Activity on #1042" class="w-[28rem] max-w-full"> <TimelineItem title="Ada Lovelace commented" time={now - 40 * MINUTE}> {#snippet icon()}<Avatar size="xs" name="Ada Lovelace" colorize />{/snippet} <Text variant="body-sm" class="rounded-shape-md bg-surface-container-low p-3 text-surface-variant-foreground" > The inner loop is twice as fast now. Can someone check the edge cases around zero? </Text> </TimelineItem> <TimelineItem title="Labels added" time={now - 3 * HOUR} tone="neutral"> {#snippet icon()} ... {/snippet} <div class="flex flex-wrap gap-2"> <Chip size="sm" variant="tonal" tone="tertiary">performance</Chip> <Chip size="sm" variant="tonal">engine</Chip> </div> </TimelineItem> <TimelineItem title="Opened" description="by Ada Lovelace" time={now - 26 * HOUR} tone="success"> {#snippet icon()} ... {/snippet} </TimelineItem></Timeline> Overridden
relative={false} writes dates in dateFormat instead of counting; a time that is not a date is shown as given. classes reaches every part — here a primary line and square markers — and the caller's own attributes stay.
<script lang="ts"> import { Timeline, TimelineItem } from 'omaris'; const now = Date.now(); const DAY = 24 * 60 * 60_000; const marker = { dot: 'rounded-xs', line: 'border-primary/40' };</script><Timeline label="Audit log" relative={false} dateFormat={{ day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }} class="w-80 max-w-full" data-log="audit"> <TimelineItem title="API key rotated" time={now - DAY} classes={marker} /> <TimelineItem title="Admin added" description="grace@example.com" time={now - 9 * DAY} classes={marker} /> <TimelineItem title="Next review" time="Every quarter" tone="neutral" pending classes={marker} /></Timeline> When to use it
Use it for
- An order's or a ticket's history — placed, paid, shipped, delivered — with the step still to come marked
pending. - An activity feed: who did what and when,
variant="compact", oneTimelineper day with aheading("Today", "Yesterday"). - An audit log with exact dates:
relative={false}and adateFormat. - Events that carry more than a line — a comment, a set of labels, a diff — as the item's children.
Not for
- Steps the person is moving through right now — a checkout, an onboarding → Stepper.
- Records to sort, filter or compare → Table.
- Live, high-volume output — a build log, a server stream → Log Viewer.
- A list of things that are not in time order → List.
Do
- Pass
timeas aDate, a timestamp or an ISO string. It is written relative to now in the page's language, marked up as<time datetime>, and kept current from one shared clock. - Use
tone="neutral"for routine events and save the colours for the ones that need attention — a failure, a refund, a delivery. - Set
maxAgeon a long feed so old events read as dates rather than "47 weeks ago". - Give the list a name: a
heading, or alabelwhen there is none.
Don't
- Mark more than one item
pending; it is the step that has not happened yet, and there is one. - Put the time in the title as text;
timeis what gives it the tooltip and the machine-readable date. - Give every item an icon. Icons pick out the events worth finding at a glance; a column of them is as flat as a column of dots.
Quick reference
variant default(default)compact
tone neutralprimary(default)secondarytertiarydestructivesuccesswarninginfo
API
Timeline
An activity feed or an order's history: events in order, each on a rail with a dot or an icon, joined by the line that makes them read as one sequence rather than a list of unrelated rows.
It is an <ol>, because the order is the information. Times are given as dates and written the way a person says them — "3 minutes ago" — with the exact moment in a real <time datetime> and in the tooltip, and they keep themselves current from one shared clock however long the feed is.
A feed grouped by day is one Timeline per day, each with a heading.
import { Timeline } from 'omaris' <Timeline heading="Today"> <TimelineItem title="Order shipped" time={shippedAt} tone="success" /> <TimelineItem title="Out for delivery" pending /></Timeline> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
heading- The group heading — "Today", "March 2026".
list- The
<ol>.
Props
variant Defaults to 'default'
TimelineVariant default gives each event room and icon-sized markers; compact is a dense feed.
heading string A heading over the list, which also names it for a screen reader — "Today", "Yesterday". Use one Timeline per group.
level Defaults to 3
2 | 3 | 4 | 5 | 6 Heading level, so the group fits the page outline.
label string Accessible name for the list when there is no heading.
relative Defaults to true
boolean Write times relative to now — "3 minutes ago". Off, they are written as dates with dateFormat. Either way the exact moment is in the <time datetime> and the tooltip.
maxAge number Past this age, in ms, a relative time switches to the date: a week is 7 * 24 * 3600_000. "47 weeks ago" is not information.
dateFormat Intl.DateTimeFormatOptions How absolute dates are written. Defaults to a long date and a short time.
locale string Language of the times. English, Arabic, Turkish and Sorani Kurdish are built in; defaults to <html lang>. Digits stay Western.
class string classes TimelineClasses Per-part Tailwind overrides. class still covers the root.
header Snippet Rich heading, in place of heading.
children Snippet TimelineItem
One event on a Timeline: a marker on the rail, a title, when it happened, and whatever else it needs underneath.
The line to the next event is drawn by this item and hidden on the last one, so a feed that grows or shrinks never leaves a line hanging. When the next item is pending the line runs dashed into it — the step that has not happened yet.
import { TimelineItem } from 'omaris' Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
rail- The column the marker and the line live in.
marker- A box the height of the title's line, so the dot centres on it.
dot- The plain dot, when there is no icon.
icon- The tonal circle an
iconsits in. line- The connector down to the next item.
content- No description in the source yet.
header- Title and time, on one line when they fit.
title- No description in the source yet.
time- No description in the source yet.
description- No description in the source yet.
body- Rich content under the description — a quote, a diff, attachments.
Props
title string What happened — "Order shipped".
description string A supporting line under the title.
time Date | string | number When it happened. A Date, a timestamp or an ISO string is written relative to now and marked up as <time datetime>; a string that is not a date ("Tomorrow, 9–12") is shown as given.
tone Defaults to 'primary'
TimelineItemTone The marker's colour role. neutral is a quiet grey dot for events that need no attention; save the tones for the ones that do.
neutral- Past events that do not need attention — the usual case in a long feed.
primarysecondarytertiarydestructivesuccesswarninginfo
pending Defaults to false
boolean The event that has not happened yet — the courier on the way, the review awaiting. Its marker pulses (and holds still under reduced motion), and the line into it is dashed. Usually the last item.
pendingLabel Defaults to 'In progress'
string Spoken after the title of a pending item.
class string classes TimelineItemClasses Per-part Tailwind overrides. class still covers the root.
icon Snippet An icon in a tonal circle, in place of the dot.
children Snippet Rich content under the description.