jixoai-ui
the terminal design language
loading the style sheet…
Skip to content
docs · print medium: screen

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

ol · order is chronology value contract · decimals first-class progress stroke · 300ms dashoffset 9-grid node · 8 logical slots plain · dashed · beam · custom payload no-JS floor · upgrades on hydration

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.

  1. 07:02

    pushed

    feat: popover family on CSS anchors · 4f2a1c

  2. 07:03

    checks passed

    12 checks · 0 failed · 8.2s

  3. 07:04

    deploying

    edge cache warming — 2 of 7 regions live

timeline-usage.svelte svelte
<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.

  1. spine rework landed

    one measured svg run path under the items — no per-item seams

  2. grid engine rebuilt

    the 5-lane grid, subgrid items, eight logical dot slots

  3. value contract drafted

    defaultValue · value · onValueChange — reui parity

  4. docs upgrade

    the twelve official families land on this page

timeline-basic-demo.svelte svelte
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.

    2026 product roadmap — v3
  1. planning

    scope frozen · staffing locked

  2. design

    geometry engine · token audit

  3. development

    twelve families · value contract

  4. testing

    probe battery · a11y pass

  5. launch

    ga · registry sync

timeline-roadmap-demo.svelte svelte
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.

  1. foundation

    • ·value contract
    • ·spine rework
    • ·registry sync
  2. families

    • ·twelve reui demos
    • ·docs upgrade
    • ·props table
  3. polish

    • ·a11y pass
    • ·perf budget
timeline-roadmap-items-demo.svelte svelte
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.

  1. placed

    order #8412 placed

    $248.00 · card ending 4242

  2. paid

    payment confirmed

    authorized · 3-verified

  3. in transit

    shipped — 2 boxes

    tracking 1Z·8842·4471·0031

  4. eta

    delivery expected

    signature on arrival

timeline-order-status-demo.svelte svelte
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.

  1. pre-seed

    $150k · 2 angels

  2. seed

    $2.4M · runway 18mo

  3. series a

    $12M · growth

  4. series b

    target $40M

timeline-milestones-demo.svelte svelte
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.

  1. checkout

    4f2a1c · clean

  2. install

    pnpm · 14.2s cached

  3. tests — running

    212 of 240 · 2 shards

  4. build

    queued behind tests

timeline-pipeline-steps-demo.svelte svelte
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.

    deployments · jixoai-ui
  1. production

    v3.4.1

    9d3b71 · edge cache warm · 7 regions

  2. staging

    v3.5.0-rc.2

    probe battery green · soak 40m

  3. preview

    pr-214

    value contract · building

timeline-deployment-log-demo.svelte svelte
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.

  1. kzf forked ui/spine-rework

    from main @ 4f2a1c

  2. pr #214 — the value contract

    9 files · +1,286 −299 · 6 checks green

  3. compare — 12 commits ahead

    spine-rework…value-contract

  4. merged into main

    squash · 4f2a1c → 9d3b71

timeline-git-activity-demo.svelte svelte
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.

  1. Ada Lovelace pushed 3 commits

    spine: measured run path · 41m ago

  2. Alan Turing opened pr #88

    density: the sm ruler · 2h ago

  3. Grace Hopper merged main

    squash · 6 checks · 3h ago

timeline-activity-feed-demo.svelte svelte
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.

  1. standup

  2. spine review

  3. probe battery

  4. docs upgrade

timeline-vertical-demo.svelte svelte
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.

  1. 2023

    launch

  2. 2024

    v2 grid engine

  3. 2025

    registry era

  4. 2026

    drawn spine

timeline-horizontal-leading-demo.svelte svelte
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.

  1. alpha

  2. beta

  3. rc

  4. ga

  5. lts

timeline-compact-milestone-demo.svelte svelte
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

  1. bsIs
    bs
    bsIe
    is
    ie
    beIs
    be
    beIe

    free spatial composition

    all eight slots authored at once — the dot stays the anchor

  2. a bare node

timeline-node-demo.svelte svelte
<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 (default) · one continuous run path
  1. plain

  2. plain

dashed · dash STARTS at the node edge
  1. alpha

  2. beta

  3. gamma

beam · a traveling light with real width
  1. live channel

  2. live channel

timeline-spine-presets-demo.svelte svelte
<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.

  1. authored spine

  2. authored spine

timeline-geometry-snippet-demo.svelte svelte
<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

value = 1.00
  1. step 1

    ≤ 1.00 — complete

  2. step 2

    > 1.00 — pending

  3. step 3

    > 1.00 — pending

  4. step 4

    > 1.00 — pending

  5. step 5

    > 1.00 — pending

  6. step 6

    > 1.00 — pending

timeline-value-stepper-demo.svelte svelte
<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

ltr (default)
  1. build

  2. test

revert
  1. build

  2. test

interlaced
  1. build

  2. test

  3. deploy

timeline-directions-demo.svelte svelte
<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.

v-ltr
  1. alpha

  2. beta

  3. gamma

v-revert
  1. alpha

  2. beta

  3. gamma

v-interlaced
  1. alpha

  2. beta

  3. gamma

h-ltr
  1. alpha

  2. beta

  3. gamma

h-revert
  1. alpha

  2. beta

  3. gamma

h-interlaced
  1. alpha

  2. beta

  3. gamma

v-ltr-rtl · dir=rtl
  1. alpha

  2. beta

  3. gamma

v-interlaced-rtl · dir=rtl
  1. alpha

  2. beta

  3. gamma

h-ltr-rtl · dir=rtl
  1. alpha

  2. beta

  3. gamma

h-revert-rtl · dir=rtl
  1. alpha

  2. beta

  3. gamma

h-interlaced-rtl · dir=rtl
  1. alpha

  2. beta

  3. gamma

v-revert-rtl · dir=rtl
  1. alpha

  2. beta

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

animation="scroll" · scroll this box
  1. commit

    commit

  2. build

    build

  3. test

    test

  4. package

    package

  5. deploy

    deploy

  6. verify

    verify

  7. announce

    announce

animation="view"
  1. alpha

  2. beta

  3. rc

  4. ga

timeline-animation-demo.svelte svelte
<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

    release train
  1. today

    released

    Build promoted.

timeline-anatomy-demo.svelte svelte
<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.

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

AttributeValueDescription
oltimeline listPreserves chronological list semantics (role=list survives list-none).
timedatetimeProvides machine-readable event time.
aria-hiddenspine svg · dot · floor lineThe 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-completedstep ≤ valueThe 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

xs
  1. event

default
  1. event

lg
  1. event

TokenDefaultSource
--jx-tl-stroke-w1pxcomponent
--jx-icondensity scaledensity
--jx-stackdensity scaledensity
--jx-gapdensity scaledensity
--jx-insetdensity scaledensity
--jx-textdensity scaledensity
--jx-text-secondarydensity scaledensity
--jx-linedensity scaledensity
--jx-line-secondarydensity scaledensity

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

  1. pushed

    12 checks · 0 failed

  2. deploying

  1. pushed

  2. live

timeline-universal.svelte svelte
<Timeline defaultValue={1} size={18} density="small">…</Timeline>

api

Timeline props

Properties

PropertyTypeDefaultDescription
defaultValue number1THE 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) numberDOM order + 1The 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) booleanfalseIn-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 ambientTimelineDot 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

PropertyTypeDefaultDescription
size'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopethe base scale — root font-size; parts size in em
shape'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto' · ambient scopecorner geometry (CSS corner-shape; §14 degrade table)
radius'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopecorner size; auto = the concentric broadcast (§3)
density'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopespacing/leading scale over the kernel channels (§4)
color'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto' · ambient scopethe hue axis of the fixed oklch primary system (§5)
theme'light' | 'dark' | 'system' | 'auto''auto' · ambient scopelight/dark profile; system = the JS-mutable global (§6)
elevation'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto' · ambient scopeofficial M3 levels over the surface ladder (§7)
motion'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto' · ambient scopeintensity across the motion kernels (§8)
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h