registry:ui · Navigation
steps — explicit ordinals, compared not registered
The wizard progress as a composed family: a nine-word state vocabulary (auto trio + middle pending, success/error/hint/emphasis/disabled) overrides the derived done/current/todo comparison when stated explicitly. The done Indicator becomes the button (a link back) when onclick is given; the current step is aria-current=step; future steps are inert spans. CSS connector; check glyph on done; the Separator self-hides on the last item.
step ordinals done marker = the button 9-word state vocabulary connector on its own lanesteps
Walk the wizard: click a completed step's check-marker to go back, or drive current from the playground — every Indicator, Title, Description and Separator below is authored, never auto-inserted. The playground rides the floating dock over the stage's top-right corner: the wizard row keeps the full stage width, and one click collapses the dock to its chrome chip when you want the corner back.
- completed connect link the git origin
- current step audit typecheck + tests green
- not started ship promote to production
<script lang="ts">
import {
Steps,
StepsItem,
StepsIndicator,
StepsTitle,
StepsDescription,
StepsSeparator,
} from '@ui/steps/index';
</script>
<!-- bind:current (0-based) is the one shared state; each Item carries a
REQUIRED explicit ordinal — state is pure step-vs-current comparison.
onclick fires only from the done state and makes the Indicator the
button (no handler ⇒ inert span, no dead affordance). -->
let current = $state(1);
<Steps bind:current>
<StepsItem step={0} label="connect" onclick={() => (current = 0)}>
<StepsIndicator />
<StepsTitle>connect</StepsTitle>
<StepsDescription>link the git origin</StepsDescription>
<StepsSeparator />
</StepsItem>
<StepsItem step={1}>
<StepsIndicator />
<StepsTitle>build</StepsTitle>
<StepsDescription>install + typecheck</StepsDescription>
<StepsSeparator />
</StepsItem>
<StepsItem step={2}>
<StepsIndicator />
<StepsTitle>ship</StepsTitle>
<StepsDescription>promote to production</StepsDescription>
<StepsSeparator />
</StepsItem>
</Steps>law
the state vocabulary — nine words for where a step stands
The derived trio (done / current / todo) is pure ordinal comparison — it cannot say submitted-but-waiting, it cannot say won or lost, it cannot say look here or closed. The state prop overrides the trio with the missing words. Read it as a wizard form AND as the markers over an NPC's head: ⋯ is the quest in flight, ! is the quest-giver, ✓ is turn-in day, ✕ is the failure state.
steps · state vocabulary
- completed donestep < current · solid fill + ✓ · connector painted primary
- current step currentstep = current · aria-current=step · solid fill + its number
- in progress pendingthe middle state · hollow + the breathing ⋯ · submitted, in flight
- succeeded successthe terminal win · ✓ on the success pair
- failed errorthe terminal failure · ✕ on the error pair
- information hintinformational · i on the info pair
- current step current · the first 1duplicates paint EVERY match current — ordinal truth, not identity
- current step current · the second 1the duplicate — also current; nothing corrupts
- information 3 (the gap)ordinal 2 simply does not exist — gaps paint none, no phantom row
- attention emphasisthe quest-giver ! · hollow + halo ring · look here
- unavailable disableddeclared out-of-reach · dashed ring, reduced contrast, spoken "unavailable" — unlike todo (the merely unreached)
- not started todostep > current · hollow ring at full contrast · inert
<script lang="ts">
import Steps, {
StepsItem,
StepsIndicator,
StepsTitle,
StepsDescription,
StepsSeparator,
} from '@ui/steps/index';
</script>
<!-- the derived trio plus the override words, three rows by state group -->
<div class={cx(rt.col24)}>
<div class={cx(rt.wFull, rt.maxW2xl)}>
<Steps current={1}>
<StepsItem step={0} label="done"><StepsIndicator /><StepsTitle>done</StepsTitle><StepsDescription>step < current · solid fill + ✓ · connector painted primary</StepsDescription><StepsSeparator /></StepsItem>
<StepsItem step={1}><StepsIndicator /><StepsTitle>current</StepsTitle><StepsDescription>step = current · aria-current=step · solid fill + its number</StepsDescription><StepsSeparator /></StepsItem>
<StepsItem step={2} state="pending"><StepsIndicator /><StepsTitle>pending</StepsTitle><StepsDescription>the middle state · hollow + the breathing ⋯ · submitted, in flight</StepsDescription><StepsSeparator /></StepsItem>
</Steps>
</div>
<div class={cx(rt.wFull, rt.maxW2xl)}>
<Steps current={1}>
<StepsItem step={0} state="success"><StepsIndicator /><StepsTitle>success</StepsTitle><StepsDescription>the terminal win · ✓ on the success pair</StepsDescription><StepsSeparator /></StepsItem>
<StepsItem step={1} state="error"><StepsIndicator /><StepsTitle>error</StepsTitle><StepsDescription>the terminal failure · ✕ on the error pair</StepsDescription><StepsSeparator /></StepsItem>
<StepsItem step={2} state="hint"><StepsIndicator /><StepsTitle>hint</StepsTitle><StepsDescription>informational · i on the info pair</StepsDescription><StepsSeparator /></StepsItem>
</Steps>
</div>
<div class={cx(rt.wFull, rt.maxW2xl)}>
<Steps current={1}>
<StepsItem step={0} state="emphasis"><StepsIndicator /><StepsTitle>emphasis</StepsTitle><StepsDescription>the quest-giver ! · hollow + halo ring · look here</StepsDescription><StepsSeparator /></StepsItem>
<StepsItem step={1} state="disabled"><StepsIndicator /><StepsTitle>disabled</StepsTitle><StepsDescription>declared out-of-reach · dashed ring, reduced contrast, spoken "unavailable" — unlike todo (the merely unreached)</StepsDescription><StepsSeparator /></StepsItem>
<StepsItem step={2}><StepsIndicator /><StepsTitle>todo</StepsTitle><StepsDescription>step > current · hollow ring at full contrast · inert</StepsDescription></StepsItem>
</Steps>
</div>
</div>types
Step states
Each item compares its explicit ordinal with current: completed, current and future are projections of one number. The explicit state prop overrides the comparison when the trio cannot say it.
steps · derived trio
- completed done
- current step current
- not started future
<script lang="ts">
import Steps, {
StepsItem,
StepsIndicator,
StepsTitle,
StepsSeparator,
} from '@ui/steps/index';
</script>
<div class={cx(rt.wFull, rt.maxW2xl)}>
<Steps current={1}>
<StepsItem step={0}><StepsIndicator /><StepsTitle>done</StepsTitle><StepsSeparator /></StepsItem>
<StepsItem step={1}><StepsIndicator /><StepsTitle>current</StepsTitle><StepsSeparator /></StepsItem>
<StepsItem step={2}><StepsIndicator /><StepsTitle>future</StepsTitle></StepsItem>
</Steps>
</div>usage
Usage
The composition contract in one sample: import the family from the registry barrel (@ui/steps/index — per-part targets exist per file), give every StepsItem its explicit step ordinal, author the parts you want. The Separator self-hides on the last item through the family css — chrome, not authoring.
<script lang="ts">
import {
Steps,
StepsItem,
StepsIndicator,
StepsTitle,
StepsDescription,
StepsSeparator,
} from '@ui/steps/index';
</script>
<!-- bind:current (0-based) is the one shared state; each Item carries a
REQUIRED explicit ordinal — state is pure step-vs-current comparison.
onclick fires only from the done state and makes the Indicator the
button (no handler ⇒ inert span, no dead affordance). -->
let current = $state(1);
<Steps bind:current>
<StepsItem step={0} label="connect" onclick={() => (current = 0)}>
<StepsIndicator />
<StepsTitle>connect</StepsTitle>
<StepsDescription>link the git origin</StepsDescription>
<StepsSeparator />
</StepsItem>
<StepsItem step={1}>
<StepsIndicator />
<StepsTitle>build</StepsTitle>
<StepsDescription>install + typecheck</StepsDescription>
<StepsSeparator />
</StepsItem>
<StepsItem step={2}>
<StepsIndicator />
<StepsTitle>ship</StepsTitle>
<StepsDescription>promote to production</StepsDescription>
<StepsSeparator />
</StepsItem>
</Steps>a11y
Accessibility
ARIA
| Attribute | Value | Description |
|---|---|---|
| aria-current | step | Marks the current step — the derived trio and an explicit state="current" both carry it. |
| sr-only status | per-item state text | The marker glyphs are aria-hidden chrome, so every item speaks its effective state as text (completed · current step · in progress · unavailable · …) — the vocabulary reaches AT as words. |
| button | completed indicator | Makes completed steps keyboard actionable only when onclick is supplied; after a go-back click, focus rests on the item (tabindex=-1), never on body. |
theming
Density and tokens
- current step step
- current step step
- current step step
| Token | Default | Source |
|---|---|---|
| --jx-icon | 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 |
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 root carries the surface (the parts ride the ambient chain — 吃也供); density rides the root's bridged provider lane, and the size axis scales the ordered list.
Steps · universal props
- completed connect
- current step build
- not started ship
- completed connect
- current step build
- not started ship
<Steps size={18} density="small">…</Steps>api
API
Steps owns current (bindable); StepsItem owns the ordinal and the state vocabulary; the parts stay authored.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
Steps.current bind | number | 0 | 0-based current ordinal. |
| StepsItem.step * | number | — | REQUIRED explicit ordinal — compared against current. |
| StepsItem.state | 'auto' | 'done' | 'current' | 'todo' | 'pending' | 'success' | 'error' | 'hint' | 'emphasis' | 'disabled' | 'auto' | The state vocabulary: auto = the derived trio (pure comparison); the semantic overrides paint the middle state (pending ⋯), the terminals (success ✓ / error ✕), hint (i), emphasis (the quest-giver !) and disabled (dashed ring, spoken "unavailable"). |
| StepsItem.label | string | — | Accessible name for the done-marker button. |
| StepsItem.onclick | (e) => void | — | Fires only from the done state — makes the Indicator the button. |
| class | string | — | Adds consumer classes. |
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) |