Skip to content

Stage Rail

Track a pipeline’s stages, with duration, progress and status for each.

import { Stage, StageRail } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a stage rail for work that moves through ordered stages: build, test and deploy in CI, the steps of a migration, the phases of a rollout. Each stage is a key in a recessed track holding its name, its duration (ticking in mono while it runs), a run of cells that fill as it progresses and its state: queued, running, passed, failed or skipped. The cells of every stage share one line, so the run reads as a single bar: ink where work is done, the accent where it's under way, the danger tone where it broke, hatched where a stage was skipped. Picking a stage raises its key, so the rail can drive whatever sits below it, such as that stage's log. For steps a person works through, use a Stepper.

import { useState } from 'react'
import { StageRail } from '@youj/react'

export function Pipeline() {
  const [stage, setStage] = useState('test')
  const testStartedAt = Date.now() - 20_000
  return (
    <StageRail
      stages={[
        { id: 'build', name: 'Build', status: 'passed', duration: 72_000 },
        { id: 'test', name: 'Test', status: 'running', startedAt: testStartedAt, estimate: 64_000 },
        { id: 'deploy', name: 'Deploy', status: 'queued' },
      ]}
      value={stage}
      onValueChange={setStage}
    />
  )
}

Times are in milliseconds. A running stage ticks from its startedAt and fills toward its estimate (what it usually takes), stopping short of full until it passes; pass progress (0–1) instead when you know it. A finished stage shows its duration, and a failed one fills to its progress, where it broke. Without value, the rail picks the running stage, else the first that failed, else the last to have run.

The rail is one toggle group with a stage always picked. Pass the id of the panel it drives as controls, and it sets aria-controls on every stage:

<StageRail stages={stages} value={picked} onValueChange={setPicked} controls="stage-log" />
<div id="stage-log" role="log" aria-label={`${name} log`}>…</div>

Reference

Propsstages, value, defaultValue… (9)

Props

StageRail props
PropType
stagesRequiredStage[]–
valuestring

The selected stage's id (controlled).

–
defaultValuestring

The stage selected at first. Default: the running stage, else the first failed, else the last.

–
onValueChange(id: string) => void–
segmentsnumber

Cells per stage.

default 12

12
labelstring

Names the rail. Default: the labels' "Pipeline stages".

–
localestring

Default: the YoujProvider locale.

–
controlsstring

The id of the panel the selection drives (a log), for aria-controls.

–
soundboolean

With sound on, picking a stage plays select.

default true

true
Other props pass through: extends ToggleGroupPrimitive.Props.
StylingParts and data attributes

Styling

Parts: stage-rail, stage-rail-stage (with data-status), stage-rail-header, stage-rail-dot, stage-rail-label, stage-rail-track, stage-rail-cell (with data-state on or off), stage-rail-footer, stage-rail-status, stage-rail-value and stage-rail-indicator. Units in the duration carry data-part="unit". The names take the theme's label voice; in Bezel the track is a recessed well, the picked stage a lit key and the figures mono.

MotionFirst view, stage change, running… · reduced motion

Motion

Motion tokens
MomentTokens
First viewthe cells light from the start of the rail, one after another, the whole rail spanning the deliberate token
duration.deliberate

560ms

560ms
Stage changethe stage’s cells take their new tone in order from its own start, so a stage that finishes washes into ink
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Runningthe next cell breathes and the dot throws a ring, looping until the stage finishes
duration.deliberate

560ms

560ms
Pickthe raised key glides to the stage (SlidingIndicator)
spring.smooth

0.36s, bounce 0

0.36s, bounce 0

Reduced motion

Cells change in place, nothing breathes and the key jumps. Durations still tick: they're information, not motion.

Reduced motion

The cells change in place with no stagger, the running cell and dot hold still, and the key jumps to the picked stage.

AccessibilityKeyboard, ARIA, announcements

Accessibility

  • It's a toggle group named by label (default "Pipeline stages"): one tab stop, arrow keys move between stages (mirrored in RTL), Home and End jump, and Enter or Space picks. The picked stage has aria-pressed="true"; pressing it again keeps it picked.
  • Each stage is named with its state and duration: "Test, Running, 42 seconds". The cells are hidden from assistive tech.
  • A stage that changes state is announced politely: "Test, Passed".
  • The state is always a word as well as a color and a dot shape.