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/reactFor 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>Description
A build, test and deploy pipeline whose stages pass in turn, with a button to run it again.
Code
stage-rail-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsstages, value, defaultValue… (9)
Props
| Prop | Type | |
|---|---|---|
stagesRequired | Stage[] | – |
value | stringThe selected stage's id (controlled). | – |
defaultValue | stringThe stage selected at first. Default: the running stage, else the first failed, else the last. | – |
onValueChange | (id: string) => void | – |
segments | numberCells per stage. default 12 | 12 |
label | stringNames the rail. Default: the labels' "Pipeline stages". | – |
locale | stringDefault: the YoujProvider locale. | – |
controls | stringThe id of the panel the selection drives (a log), for | – |
sound | booleanWith sound on, picking a stage plays 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
| Moment | Tokens | ||
|---|---|---|---|
| First viewthe cells light from the start of the rail, one after another, the whole rail spanning the deliberate token | duration.deliberate560ms | 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.standard140ms · 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.deliberate560ms | 560ms | |
| Pickthe raised key glides to the stage (SlidingIndicator) | spring.smooth0.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 hasaria-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.