registry:ui · Data display
timeline — the drawn activity spine
The activity stream with a DRAWN spine: a one-cell grid host stacks the semantic ol over one whole-list SVG spine layer measured from the live geometry (grid-area 1/1 sibling, source-order under the items, pointer-transparent, zero abspos) — connectors as dot-edge-to-dot-edge subpaths of one path element (the axis never crosses a dot, Owner r3), dashed as real stroke-dasharray phase-anchored to the node's flow-end edge, beam as a traveling gradient stroke with actual width, scroll-progress as a stroke-dashoffset draw. axis/direction/interlacing/RTL resolve in one coordinate space; a no-JS per-item CSS floor upgrades to the measured spine on hydration. The spine prop takes 'plain' | 'dashed' | 'beam' or a custom snippet receiving the geometry payload (the line(i) seam and the line-preset components retired, breaking).
timeline
The spine is DRAWN: one whole-list SVG layer, measured from the live item geometry, painted under the dots and content by source order in a one-cell grid host. The value contract drives it: items whose step ≤ the current value paint data-completed (dot fills primary, title steps to full ink), and the progress stroke draws along the measured path to the value's position — decimals land between nodes. Before hydration (and without JS forever) every item carries the plain CSS floor line.
- 07:02
pushed
feat: popover family on CSS anchors · 4f2a1c
- 07:03
checks passed
12 checks · 0 failed · 8.2s
- 07:04
deploying
edge cache warming — 2 of 7 regions live
<Timeline value={2.5}>
<TimelineHeader>release train · week 37</TimelineHeader>
<TimelineItem>
<!-- the 9-grid node: blockStart rides the spine as a labeled cutout -->
<TimelineDot>
{#snippet blockStart()}<span>07:02</span>{/snippet}
</TimelineDot>
<TimelineContent>
<TimelineTime datetime="2026-08-22T07:02:00Z">07:02</TimelineTime>
<TimelineTitle>pushed</TimelineTitle>
<p>12 checks · 0 failed · 8.2s</p><!-- free children = the body -->
</TimelineContent>
</TimelineItem>
<TimelineItem pending={true}><!-- hollow dot + muted title (attribute paint) -->
<TimelineDot variant="ring" />
<TimelineContent>
<TimelineTitle>deploying</TimelineTitle>
</TimelineContent>
</TimelineItem>
</Timeline>
<!-- THE VALUE CONTRACT (reui parity): defaultValue seeds the current
step (default 1), value controls it, onValueChange reports every
setStep change. DECIMALS ARE FIRST-CLASS — 1.5 completes item 1,
not item 2, and paints the progress stroke halfway to node 2
(300ms dashoffset transition; reduced motion drops the transition,
the position stays). Items take step to declare ladder positions
(default DOM order + 1, strictly ascending). Under
animation='scroll' the scroller owns the stroke channel; the
value keeps driving the discrete data-completed paint.
<Timeline value={stepper} onValueChange={(v) => (stepper = v)}>
<TimelineItem step={1}>…</TimelineItem>
<TimelineItem step={4}>…</TimelineItem><!-- a declared gap interpolates -->
</Timeline>
<!-- the SPINE SEAM: presets by name, or a custom snippet receiving the
measured geometry payload (node centers in list-root coordinates,
flow order · axis/direction/rtl metadata · per-segment path data ·
the density scale). The retired line(i) per-item seam is gone.
<Timeline spine="dashed"><!-- or "beam" | "plain" (default) -->
<Timeline>
{#snippet spine(geometry)}
<path d={geometry.runPath} fill="none" stroke="var(--primary)" stroke-width="1.5" />
{/snippet}
…
</Timeline> -->the official twelve · chronology
the chronology forms — basic · roadmap · roadmap items
The twelve official reui timeline families, one canvas each, stage labels pinned to the family inventory. The chronology forms are the reading rail: dates and titles under the value contract, phases with the current one mid-way, phases carrying item lists. Every family is the same engine — only the content and the value change.
timeline · basic
Dates, titles, bodies. defaultValue 2 completes the first two entries — the rest stay muted until the value moves.
-
spine rework landed
one measured svg run path under the items — no per-item seams
-
grid engine rebuilt
the 5-lane grid, subgrid items, eight logical dot slots
-
value contract drafted
defaultValue · value · onValueChange — reui parity
-
docs upgrade
the twelve official families land on this page
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<Timeline defaultValue={2}>
<TimelineItem>
<TimelineDot />
<TimelineContent>
<TimelineTime datetime="2026-08-30T09:00:00Z">Aug 30</TimelineTime>
<TimelineTitle>spine rework landed</TimelineTitle>
<p>one measured svg run path under the items</p>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineDot />
<TimelineContent>
<TimelineTime datetime="2026-09-01T09:00:00Z">Sep 1</TimelineTime>
<TimelineTitle>grid engine rebuilt</TimelineTitle>
</TimelineContent>
</TimelineItem>
<!-- defaultValue 2: the first two items paint data-completed -->
</Timeline>timeline · roadmap
Roadmap phases with TimelineHeader chrome. value 2.5 — a decimal: planning and design complete, development in flight, the progress stroke halfway between nodes 2 and 3.
-
planning
scope frozen · staffing locked
-
design
geometry engine · token audit
-
development
twelve families · value contract
-
testing
probe battery · a11y pass
-
launch
ga · registry sync
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<Timeline value={2.5}>
<TimelineHeader>2026 product roadmap — v3</TimelineHeader>
<TimelineItem><TimelineDot /><TimelineContent>
<TimelineTime datetime="2026-01-01">Q1</TimelineTime>
<TimelineTitle>planning</TimelineTitle>
</TimelineContent></TimelineItem>
<!-- value 2.5: planning + design complete, development in flight —
the progress stroke sits HALFWAY between nodes 2 and 3 -->
<TimelineItem pending><TimelineDot /><TimelineContent>
<TimelineTitle>development</TimelineTitle>
</TimelineContent></TimelineItem>
</Timeline>timeline · roadmap items
Phases that carry item lists — free body children under interlaced zones; the current phase pending.
-
foundation
- ·value contract
- ·spine rework
- ·registry sync
-
families
- ·twelve reui demos
- ·docs upgrade
- ·props table
-
polish
- ·a11y pass
- ·perf budget
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<Timeline defaultValue={2} direction="interlaced">
<TimelineItem>
<TimelineDot />
<TimelineContent>
<TimelineTime datetime="2026-07-01">Q3</TimelineTime>
<TimelineTitle>foundation</TimelineTitle>
<ul><li>value contract</li><li>spine rework</li></ul>
</TimelineContent>
</TimelineItem>
<!-- both zones stay live under interlaced; the free body is a list -->
</Timeline>the official twelve · tracking
the tracking forms — order status · milestones · pipeline · deployment log
Progress-as-value: an order partway through fulfillment, funding rounds behind you, a CI pipeline with the current stage in flight (the pending dot hosts a spinner in its children), and a deployment log stamped with environment badges.
timeline · order status
Order tracking with status badges and amounts. value 2.4 — paid and placed complete, the stroke 40% of the way to shipped.
- placed
order #8412 placed
$248.00 · card ending 4242
- paid
payment confirmed
authorized · 3-verified
- in transit
shipped — 2 boxes
tracking 1Z·8842·4471·0031
- eta
delivery expected
signature on arrival
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
import Badge from '@ui/badge';
</script>
<Timeline value={2.4}>
<TimelineItem>
<TimelineDot />
<TimelineContent>
<div class="flex items-baseline justify-between gap-3">
<TimelineTime datetime="2026-01-07T10:12:00Z">Jan 7, 10:12</TimelineTime>
<Badge variant="outline">placed</Badge>
</div>
<TimelineTitle>order #8412</TimelineTitle>
<p>$248.00 · card ending 4242</p>
</TimelineContent>
</TimelineItem>
<!-- value 2.4: the stroke runs 40% of the way from node 2 to node 3 -->
</Timeline>timeline · milestones
Funding rounds with dates — round dots, defaultValue 3 completes through series a; the upcoming round stays pending.
-
pre-seed
$150k · 2 angels
-
seed
$2.4M · runway 18mo
-
series a
$12M · growth
-
series b
target $40M
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<Timeline defaultValue={3}>
<TimelineItem>
<TimelineDot variant="round" />
<TimelineContent>
<TimelineTime datetime="2023-11-01">Nov 2023</TimelineTime>
<TimelineTitle>pre-seed</TimelineTitle>
<p>$150k · 2 angels</p>
</TimelineContent>
</TimelineItem>
<!-- round dots + dates; defaultValue 3 completes through series a -->
</Timeline>timeline · pipeline steps
CI/CD pipeline — checkout and install complete; tests in flight: the pending hollow dot carries a spinner in its children.
-
checkout
4f2a1c · clean
-
install
pnpm · 14.2s cached
-
tests — running
212 of 240 · 2 shards
-
build
queued behind tests
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
import Spin from '@ui/spin';
</script>
<Timeline defaultValue={2}>
<TimelineItem><TimelineDot variant="round" /><TimelineContent>
<TimelineTitle>checkout</TimelineTitle><p>4f2a1c · clean</p>
</TimelineContent></TimelineItem>
<TimelineItem><TimelineDot variant="round" /><TimelineContent>
<TimelineTitle>install</TimelineTitle><p>pnpm · 14.2s cached</p>
</TimelineContent></TimelineItem>
<!-- pending WINS the dot paint: hollow node, spinner in its children -->
<TimelineItem pending>
<TimelineDot variant="round"><Spin spinner="bars-scale" size={9} label="tests running" /></TimelineDot>
<TimelineContent><TimelineTitle>tests — running</TimelineTitle><p>212 of 240</p></TimelineContent>
</TimelineItem>
<TimelineItem><TimelineDot variant="round" /><TimelineContent>
<TimelineTitle>build</TimelineTitle><p>queued</p>
</TimelineContent></TimelineItem>
</Timeline>timeline · deployment log
Deploy log entries with environment badges — fill for production, tonal for staging, outline for preview; the preview build pending.
- production
v3.4.1
9d3b71 · edge cache warm · 7 regions
- staging
v3.5.0-rc.2
probe battery green · soak 40m
- preview
pr-214
value contract · building
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
import Badge from '@ui/badge';
</script>
<Timeline defaultValue={2}>
<TimelineHeader>deployments · jixoai-ui</TimelineHeader>
<TimelineItem>
<TimelineDot variant="round" />
<TimelineContent>
<div class="flex items-baseline justify-between gap-3">
<TimelineTime datetime="2026-09-15T09:41:00Z">09:41</TimelineTime>
<Badge variant="fill">production</Badge>
</div>
<TimelineTitle>v3.4.1</TimelineTitle>
<p>9d3b71 · edge cache warm</p>
</TimelineContent>
</TimelineItem>
</Timeline>the official twelve · git + activity
the git + activity forms — icon badges · avatars
The node's children lane carries richer marks than a filled square: lucide git glyphs tinted per event type, and the repo avatar's initials fallback as the face of each feed entry.
timeline · git activity
Git events as icon badges — fork, pull request, compare, merge — one lucide glyph inside each node, a distinct token tint per type.
-
kzf forked ui/spine-rework
from main @ 4f2a1c
-
pr #214 — the value contract
9 files · +1,286 −299 · 6 checks green
-
compare — 12 commits ahead
spine-rework…value-contract
-
merged into main
squash · 4f2a1c → 9d3b71
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
import Icon from '@ui/icon';
</script>
<Timeline value={3}>
<TimelineItem>
<!-- the icon pattern: children render INSIDE the node span — the
node grows around them; tinted constants ride inline styles -->
<TimelineDot variant="round" style="border-color: var(--info); background: color-mix(in oklab, var(--info) 14%, transparent); color: var(--info)">
<Icon name="lucide:git-fork" size={11} />
</TimelineDot>
<TimelineContent><TimelineTitle>kzf forked ui/spine-rework</TimelineTitle></TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineDot variant="round" style="border-color: var(--success); background: color-mix(in oklab, var(--success) 14%, transparent); color: var(--success)">
<Icon name="lucide:git-pull-request" size={11} />
</TimelineDot>
<TimelineContent><TimelineTitle>pr #214 — the value contract</TimelineTitle></TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineDot variant="round" style="border-color: var(--warning); background: color-mix(in oklab, var(--warning) 14%, transparent); color: var(--warning)">
<Icon name="lucide:git-compare-arrows" size={11} />
</TimelineDot>
<TimelineContent><TimelineTitle>compare — 12 commits ahead</TimelineTitle></TimelineContent>
</TimelineItem>
<TimelineItem pending>
<TimelineDot variant="round" style="border-color: var(--primary); background: color-mix(in oklab, var(--primary) 14%, transparent); color: var(--primary)">
<Icon name="lucide:git-merge" size={11} />
</TimelineDot>
<TimelineContent><TimelineTitle>merged into main</TimelineTitle></TimelineContent>
</TimelineItem>
</Timeline>timeline · activity feed
An activity feed with avatars — the repo avatar renders inside each node (initials fallback, no image needed); the merge in flight stays pending.
-
Ada Lovelace pushed 3 commits
spine: measured run path · 41m ago
-
Alan Turing opened pr #88
density: the sm ruler · 2h ago
-
Grace Hopper merged main
squash · 6 checks · 3h ago
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
import Avatar from '@ui/avatar';
</script>
<Timeline value={3}>
<TimelineItem>
<!-- avatars as node children: the repo avatar's initials fallback
needs no image — alt="" marks it decorative beside the name -->
<TimelineDot variant="round">
<Avatar name="Ada Lovelace" size="sm" alt="" tooltip={false} />
</TimelineDot>
<TimelineContent>
<TimelineTitle>Ada Lovelace pushed 3 commits</TimelineTitle>
<p>spine: measured run path · 41m ago</p>
</TimelineContent>
</TimelineItem>
</Timeline>the official twelve · axis forms
the axis forms — vertical · horizontal leading labels · compact milestone
The engine transposes whole: the plain vertical rail (here at sm density), the horizontal axis with labels leading each node on the line, and the compact horizontal milestone strip.
timeline · vertical
The plain vertical form at sm density — the whole ruler steps down: dots, gaps, text.
-
standup
-
spine review
-
probe battery
-
docs upgrade
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<Timeline defaultValue={2} density="sm">
<TimelineItem><TimelineDot /><TimelineContent>
<TimelineTime datetime="09:02">09:02</TimelineTime>
<TimelineTitle>standup</TimelineTitle>
</TimelineContent></TimelineItem>
<!-- density sm: the whole ruler steps down — dots, gaps, text -->
</Timeline>timeline · horizontal leading labels
axis horizontal with a label leading each node — the blockStart slot flanks the dot against the flow on the transposed engine.
- 2023
launch
- 2024
v2 grid engine
- 2025
registry era
- 2026
drawn spine
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<div class="w-full overflow-x-auto border border-border p-6">
<Timeline axis="horizontal" defaultValue={3} class="min-w-[34rem]">
<TimelineItem>
<!-- blockStart rides BEFORE the node along the line — the
leading label; on the horizontal axis it flanks the dot -->
<TimelineDot variant="round">
{#snippet blockStart()}<span>2023</span>{/snippet}
</TimelineDot>
<TimelineContent><TimelineTitle>launch</TimelineTitle></TimelineContent>
</TimelineItem>
</Timeline>
</div>timeline · compact horizontal milestone
The low-profile strip — horizontal axis, sm density, bare titles; defaultValue 2 lights the first two stops.
-
alpha
-
beta
-
rc
-
ga
-
lts
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
<div class="w-full overflow-x-auto border border-border p-4">
<Timeline axis="horizontal" defaultValue={2} density="sm" class="min-w-[30rem]">
<TimelineItem>
<TimelineDot variant="round" />
<TimelineContent><TimelineTitle>alpha</TimelineTitle></TimelineContent>
</TimelineItem>
<!-- sm density + bare titles: the compact milestone strip -->
</Timeline>
</div>composition
the 9-grid node — eight logical slots around the dot
Every dot is the center of a 3×3 grid. blockStart/blockEnd ride the SPINE channel as labeled cutouts (their opaque ground interrupts the drawn spine, by essence); inlineStart/inlineEnd flank the dot; the four corners complete the compass. Logical names never change meaning when the axis flips.
timeline · node slots
- bsIsbsbsIeisiebeIsbebeIe
free spatial composition
all eight slots authored at once — the dot stays the anchor
-
a bare node
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<div class="w-full max-w-md border border-border p-6">
<Timeline>
<TimelineItem>
<TimelineDot>
{#snippet blockStartInlineStart()}<span class="text-[10px]">bsIs</span>{/snippet}
{#snippet blockStart()}<span>bs</span>{/snippet}
{#snippet blockStartInlineEnd()}<span class="text-[10px]">bsIe</span>{/snippet}
{#snippet inlineStart()}<span>is</span>{/snippet}
{#snippet inlineEnd()}<span>ie</span>{/snippet}
{#snippet blockEndInlineStart()}<span class="text-[10px]">beIs</span>{/snippet}
{#snippet blockEnd()}<span>be</span>{/snippet}
{#snippet blockEndInlineEnd()}<span class="text-[10px]">beIe</span>{/snippet}
</TimelineDot>
<TimelineContent>
<TimelineTitle>free spatial composition</TimelineTitle>
<p class="text-[12.5px] text-muted-foreground">all eight slots authored at once — the dot stays the anchor</p>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineDot />
<TimelineContent><TimelineTitle>a bare node</TimelineTitle></TimelineContent>
</TimelineItem>
</Timeline>
</div>seam
the spine — drawn, not backgrounded
One whole-list SVG layer paints the spine: connectors run dot-edge to dot-edge as per-gap subpaths of ONE path element (the axis never crosses a dot); dashed is a real stroke-dasharray whose phase anchors a dash START at the node's flow-end edge — at every density, because the anchor is measured; beam is a stroked gradient segment with actual width and soft edges. The spine prop takes a preset name or a custom snippet receiving the measured geometry payload.
timeline · spine presets
-
plain
-
plain
-
alpha
-
beta
-
gamma
-
live channel
-
live channel
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineContent,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<div class="grid gap-6 min-[1100px]:grid-cols-3">
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">plain (default) · one continuous run path</span>
<Timeline spine="plain">
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>plain</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>plain</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">dashed · dash STARTS at the node edge</span>
<Timeline spine="dashed">
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>alpha</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>beta</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>gamma</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">beam · a traveling light with real width</span>
<Timeline spine="beam">
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>live channel</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>live channel</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>
</div>timeline · custom geometry
The custom seam: a snippet receiving the measured TimelineSpineGeometry — node centers in list-root coordinates, per-segment path data, the stops table — rendering authored svg straight into the spine layer.
-
authored spine
-
authored spine
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineContent,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<div class="w-full max-w-md border border-border p-6">
<Timeline>
{#snippet spine(geometry)}
<path
d={geometry.runPath}
fill="none"
stroke="var(--primary)"
stroke-width="1.5"
stroke-dasharray="2 6"
stroke-linecap="round"
/>
{#each geometry.nodes as node}
<circle cx={node.x} cy={node.y} r={geometry.nodeRadius + 3} fill="none" stroke="var(--primary)" stroke-width="1" opacity="0.4" />
{/each}
{/snippet}
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>authored spine</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>authored spine</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>the value contract
the value contract — decimal progress + the controlled stepper
One number owns the chronology: defaultValue seeds it, value controls it, onValueChange reports every change. Decimals are first-class — 1.5 completes item 1, not item 2, and paints the progress stroke halfway between nodes 1 and 2 (a 300ms dashoffset transition; reduced motion drops the transition, the position stays). The stepper drives the same number the reui demos do: next/prev step by whole milestones, random lands between them, and play tweens 1 → 6 through the fractional mid-states.
timeline · value contract
-
step 1
≤ 1.00 — complete
-
step 2
> 1.00 — pending
-
step 3
> 1.00 — pending
-
step 4
> 1.00 — pending
-
step 5
> 1.00 — pending
-
step 6
> 1.00 — pending
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineContent,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<!-- the controlled stepper: ONE state number drives value; decimals
ride straight through — the stroke tweens, the thresholds flip -->
let stepper = $state(1);
const STEPS = 6;
function stepNext() { stepper = Math.min(STEPS, Math.floor(stepper) + 1); }
function stepPrev() { stepper = Math.max(1, Math.ceil(stepper) - 1); }
function stepRandom() { stepper = 1 + Math.random() * (STEPS - 1); }
<div class="flex flex-wrap items-center gap-2">
<button onclick={stepPrev}>prev</button>
<button onclick={stepNext}>next</button>
<button onclick={() => (stepper = 1)}>reset</button>
<button onclick={stepRandom}>random</button>
<span>value = {stepper.toFixed(2)}</span>
</div>
<Timeline value={stepper}>
{#each Array(STEPS) as _, i (i)}
<TimelineItem>
<TimelineDot />
<TimelineContent>
<TimelineTitle>step {i + 1}</TimelineTitle>
<p>{i + 1 <= stepper ? 'complete' : 'upcoming'}</p>
</TimelineContent>
</TimelineItem>
{/each}
</Timeline>layout
direction — ltr · revert · interlaced
Direction picks which zone(s) the content takes: ltr collapses the start zone (the classic reading rail), revert mirrors it, interlaced keeps both zones and alternates item by item — the center-stage chronology. Zones are grid tracks (minmax(0,0fr) collapses a lane), never margins.
timeline · directions
-
build
-
test
-
build
-
test
-
build
-
test
-
deploy
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<div class="grid gap-6 min-[1100px]:grid-cols-3">
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">ltr (default)</span>
<Timeline direction="ltr">
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>build</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>test</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">revert</span>
<Timeline direction="revert">
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>build</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>test</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">interlaced</span>
<Timeline direction="interlaced">
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>build</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>test</TimelineTitle></TimelineContent></TimelineItem>
<TimelineItem><TimelineDot /><TimelineContent><TimelineTitle>deploy</TimelineTitle></TimelineContent></TimelineItem>
</Timeline>
</div>
</div>geometry
the geometry matrix — axis × direction × RTL in one coordinate space
The measurement runtime emits node centers in list-root coordinates in FLOW order, with axis/direction/interlaced/rtl metadata; RTL resolves in the coordinate transform (physical geometry, logical chronology) — a horizontal RTL list draws its run right-to-left because the path starts at the chronologically-first node, with zero mirror branches. Every cell of the matrix carries a data-variant hook for the probe battery.
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
-
alpha
-
beta
-
gamma
motion
animation — view-driven entries, scroll-drawn progress
animation='view' gives every item a rise entrance as it enters the scrollport; animation='scroll' draws the progress stroke along the measured run path with the nearest scroller — a stroke-dashoffset draw-on that starts at the chronologically-first node (RTL needs no branch). Both are scroll-driven CSS (@supports-gated): engines without the timeline APIs render the final state, and reduced motion removes the decorative motion. Under scroll mode the scroller OWNS the stroke channel — the value contract still drives the discrete data-completed paint, but no value dashoffset is painted.
timeline · animation
- commit
commit
- build
build
- test
test
- package
package
- deploy
deploy
- verify
verify
- announce
announce
-
alpha
-
beta
-
rc
-
ga
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<div class="flex w-full flex-col gap-6">
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">animation="scroll" · scroll this box</span>
<div class="max-h-64 overflow-y-auto border border-border p-6">
<Timeline animation="scroll">
{#each ['commit', 'build', 'test', 'package', 'deploy', 'verify', 'announce'] as phase (phase)}
<TimelineItem>
<TimelineDot>
{#snippet blockStart()}<span>{phase}</span>{/snippet}
</TimelineDot>
<TimelineContent><TimelineTitle>{phase}</TimelineTitle></TimelineContent>
</TimelineItem>
{/each}
</Timeline>
</div>
</div>
<div class="flex flex-col gap-2">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">animation="view"</span>
<div class="max-h-64 overflow-y-auto border border-border p-6">
<Timeline animation="view">
{#each ['alpha', 'beta', 'rc', 'ga'] as phase (phase)}
<TimelineItem>
<TimelineDot variant="round" />
<TimelineContent><TimelineTitle>{phase}</TimelineTitle></TimelineContent>
</TimelineItem>
{/each}
</Timeline>
</div>
</div>
</div>types
Timeline anatomy
Timeline is composition-first: header, item, dot (the 9-grid node), content, time, title and free-form body remain independent parts; the spine is a measured layer, never authored markup. The value contract and the per-item pending flag are ATTRIBUTE paint — the parts stay stateless.
timeline · anatomy
- today
released
Build promoted.
<script lang="ts">
import Timeline, {
TimelineItem,
TimelineDot,
TimelineHeader,
TimelineContent,
TimelineTime,
TimelineTitle,
} from '@ui/timeline/index';
</script>
<div class="max-w-md">
<Timeline>
<TimelineHeader>release train</TimelineHeader>
<TimelineItem>
<TimelineDot>{#snippet blockStart()}<span>today</span>{/snippet}</TimelineDot>
<TimelineContent>
<TimelineTime datetime="2026-09-01">today</TimelineTime>
<TimelineTitle>released</TimelineTitle>
<p>Build promoted.</p>
</TimelineContent>
</TimelineItem>
</Timeline>
</div>usage
Usage
The composition contract in one sample: import the family from the registry barrel (@ui/timeline/index — per-part targets exist per file), author one TimelineItem per entry with the parts you need. There is no items[] prop and no body snippet — the body is plain children. The value contract rides the root: defaultValue seeds, value controls, decimals included.
<Timeline defaultValue={2}>
<TimelineHeader>release train · week 37</TimelineHeader>
<TimelineItem>
<!-- the 9-grid node: blockStart rides the spine as a labeled cutout -->
<TimelineDot>
{#snippet blockStart()}<span>07:02</span>{/snippet}
</TimelineDot>
<TimelineContent>
<TimelineTime datetime="2026-08-22T07:02:00Z">07:02</TimelineTime>
<TimelineTitle>pushed</TimelineTitle>
<p>12 checks · 0 failed · 8.2s</p><!-- free children = the body -->
</TimelineContent>
</TimelineItem>
<TimelineItem pending><!-- hollow dot + muted title (attribute paint) -->
<TimelineDot variant="ring" />
<TimelineContent>
<TimelineTitle>deploying</TimelineTitle>
</TimelineContent>
</TimelineItem>
</Timeline>
<!-- THE VALUE CONTRACT (reui parity): defaultValue seeds the current
step (default 1), value controls it, onValueChange reports every
setStep change. DECIMALS ARE FIRST-CLASS — 1.5 completes item 1,
not item 2, and paints the progress stroke halfway to node 2
(300ms dashoffset transition; reduced motion drops the transition,
the position stays). Items take step to declare ladder positions
(default DOM order + 1, strictly ascending). Under
animation='scroll' the scroller owns the stroke channel; the
value keeps driving the discrete data-completed paint.
<Timeline value={stepper} onValueChange={(v) => (stepper = v)}>
<TimelineItem step={1}>…</TimelineItem>
<TimelineItem step={4}>…</TimelineItem><!-- a declared gap interpolates -->
</Timeline>
<!-- the SPINE SEAM: presets by name, or a custom snippet receiving the
measured geometry payload (node centers in list-root coordinates,
flow order · axis/direction/rtl metadata · per-segment path data ·
the density scale). The retired line(i) per-item seam is gone.
<Timeline spine="dashed"><!-- or "beam" | "plain" (default) -->
<Timeline>
{#snippet spine(geometry)}
<path d={geometry.runPath} fill="none" stroke="var(--primary)" stroke-width="1.5" />
{/snippet}
…
</Timeline> -->a11y
Accessibility
ARIA
| Attribute | Value | Description |
|---|---|---|
| ol | timeline list | Preserves chronological list semantics (role=list survives list-none). |
| time | datetime | Provides machine-readable event time. |
| aria-hidden | spine svg · dot · floor line | The drawn spine, the dots and the floor lines are decoration — the svg layer is pointer-transparent too. The cutout SLOTS are readable content — they carry text and are never hidden. The progress stroke is the same decorative layer: the value it paints is state, exposed through data-completed semantics on the items, never through the stroke alone. |
| data-completed | step ≤ value | The value contract's discrete paint — a completed milestone is legible with styles off (the attribute is on the li), and the in-flight pending flag stays the louder per-item channel. |
theming
Density and tokens
-
event
-
event
-
event
| Token | Default | Source |
|---|---|---|
| --jx-tl-stroke-w | 1px | component |
| --jx-icon | density scale | density |
| --jx-stack | density scale | density |
| --jx-gap | density scale | density |
| --jx-inset | density scale | density |
| --jx-text | density scale | density |
| --jx-text-secondary | density scale | density |
| --jx-line | density scale | density |
| --jx-line-secondary | density scale | density |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. The ONE-CELL GRID HOST is the family's own DOM root; the measured spine (the drawn SVG) is engine geometry outside the supply set, and the items ride the ambient chain. The dot's corner grammar keeps its own variant literal beside the §2 axis (no value-space collision).
Timeline · universal props
-
pushed
12 checks · 0 failed
-
deploying
-
pushed
-
live
<Timeline defaultValue={1} size={18} density="small">…</Timeline>api
Timeline props
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| defaultValue | number | 1 | THE VALUE CONTRACT — the uncontrolled current step's seed (reui parity). Decimals are first-class and never rounded: 1.5 completes item 1, not item 2, and paints the progress stroke halfway between nodes 1 and 2. |
| value | number | — | The controlled current step — overrides the read side while setStep reports through onValueChange. The value maps onto the measured run through the STOPS milestone table as the progress stroke (300ms stroke-dashoffset transition; reduced motion drops the transition, the position stays). Under animation='scroll' the scroller owns the stroke channel — the value then drives only the discrete data-completed paint. |
| onValueChange | (v: number) => void | — | Fires on every setStep change — same-value repeats included (reui's exact semantics; the consumer decides what changed). |
| step (TimelineItem) | number | DOM order + 1 | The item's declared ladder position. Strictly ascending in DOM order — a duplicate is owned by its later item (dev-warned); the milestone table dedupes for the stroke math while both items keep the discrete completed paint. Declared gaps (e.g. 1 then 4) interpolate across the gap's arc. |
| axis | 'vertical' | 'horizontal' | 'vertical' | The flow axis; the engine transposes, slot names stay logical. |
| direction | 'ltr' | 'revert' | 'interlaced' | 'ltr' | Which zone(s) content takes; interlaced alternates item by item. |
| animation | 'none' | 'view' | 'scroll' | 'none' | view = per-item entrance as it enters the scrollport; scroll = the progress stroke draws on with the nearest scroller (the scroller then OWNS the stroke; the value keeps the discrete paint). Both @supports-gated. |
| spine | 'plain' | 'dashed' | 'beam' | Snippet<[TimelineSpineGeometry]> | 'plain' | The drawn spine: a preset by name, or a custom snippet receiving the measured geometry payload (node centers in list-root coordinates in flow order · axis/direction/interlaced/rtl metadata · per-segment path data with the dot-edge phase anchor · the STOPS table + pathLength · the density scale). The snippet renders inside the spine svg — author path/circle/… directly. BREAKING successor of the retired line(i) seam. |
| pending (TimelineItem) | boolean | false | In-flight entry: hollow dot + muted title (attribute paint — the LOUDER channel, winning over data-completed while both stay legible with styles off). |
| variant | 'square' | 'round' | 'ring' | 'square' · Own default, not ambient | TimelineDot corner grammar. Defaults: literal slot — own ’square’, not ambient (the dot is outside the paint zone’s frozen availability table). Dot children (the icon/avatar pattern) render INSIDE the node — centered, the node grows around them, the min size keeps. |
| class | string | — | Adds consumer classes (lands on the grid host — the component root). |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |