Skip to content
omaris

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'
Learn

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.

  1. Order placed

  2. Payment confirmed

    Visa ending 4242

  3. Shipped from Erbil

    Aramex AX-99120-IQ

  4. Out for delivery, In progress

<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

  1. Ada merged #1042

  2. Grace commented on #1040

Yesterday

  1. Build failed on main

  2. Release 0.9.2 published

<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.

  1. Ada Lovelace commented

    The inner loop is twice as fast now. Can someone check the edge cases around zero?

  2. Labels added

    performance engine
  3. Opened

    by Ada Lovelace

<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.

  1. API key rotated

  2. Admin added

    grace@example.com

  3. Next review, In progress

    Every quarter
<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", one Timeline per day with a heading ("Today", "Yesterday").
  • An audit log with exact dates: relative={false} and a dateFormat.
  • 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 time as a Date, 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 maxAge on a long feed so old events read as dates rather than "47 weeks ago".
  • Give the list a name: a heading, or a label when 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; time is 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
  • neutral
  • primary (default)
  • secondary
  • tertiary
  • destructive
  • success
  • warning
  • info

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 icon sits 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.
primary
secondary
tertiary
destructive
success
warning
info
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.