Stepper
Steppers display progress through a sequence of logical and numbered steps. Use them for multi-step workflows like forms, onboarding flows, or checkout processes where users need to see their position and the steps ahead. Rendered as an ordered list (not a navigation landmark).
Installation
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
Requires React 19+, base CSS and a Theme provider. See Setup.
Step
Individual step within a Stepper. Renders a progress-bar segment, an indicator, and a label with optional description. Progress (completed/active/not-started) is derived from the parent Stepper's activeStep and this step's step index.
| Prop | Type | Default | Description |
|---|---|---|---|
| step * | number | Zero-based index of this step. Used to derive progress (completed/active/not-started) relative to the parent activeStep. | |
| label * | string | Step label text. Kept to a single line and ellipsized when the step is narrower than the label, so a row of horizontal steps stays the same height and the track under them stays straight. The full string is still read out as part of the step, so a truncated label costs nothing in the accessible name — but short labels survive narrow layouts better. | |
| description | string | Optional description shown below the label for additional context. | |
| children | ReactNode | Content rendered below the label and description. Useful in vertical steppers for form fields or detailed step content. In a compact horizontal Stepper, the content remains mounted to preserve local state while it is hidden with the expanded step details. | |
| status | 'accent' | 'success' | 'warning' | 'error' | Semantic color for the step. Controls color only and maps to the global Astryx semantic tokens. Leave unset for the progress-derived default coloring. | |
| indicator | 'auto' | 'number' | 'none' | ReactNode | 'auto' | What to show as the step indicator. 'auto' shows a number until completed then a check, 'number' always shows a numbered badge, 'none' hides it, or pass any ReactNode (e.g. an Icon) for a fully custom indicator. |
| isDisabled | boolean | false | Disables interaction and dims the step indicator and label. |
| isOptional | boolean | false | Marks the step as optional, appending an "Optional" affordance after the label. |
| endContent | ReactNode | Trailing content rendered at the end of the label row. | |
| density | 'compact' | 'balanced' | 'spacious' | Controls vertical padding of the step. Falls back to the stepper-level density when unset. |
Stepper
Steppers display progress through a sequence of logical and numbered steps. Use them for multi-step workflows like forms, onboarding flows, or checkout processes where users need to see their position and the steps ahead. Rendered as an ordered list (not a navigation landmark).
Usage guidance
- Keep step labels short and descriptive: "Payment" not "Enter your payment information".
- Use the vertical orientation when steps carry longer descriptions. A horizontal stepper handles narrow containers itself: once the frame gives each step less than horizontalOptions.minimumStepWidth (112px by default) it drops the labels for a segmented track and uses the configured collapsedVariant beneath it.
- Set horizontalOptions.collapsedVariant to 'withLabel' when the page already supplies Back/Continue, or to 'hiddenLabel' when surrounding UI owns both the current-step heading and navigation and only a bare progress track is needed.
- Provide onStepClick for non-linear workflows where users may need to revisit earlier steps.
- Use status only to apply a semantic color (accent/success/warning/error); pass a custom icon for richer indicators.
- Avoid: Use a stepper for fewer than 3 steps; a simple heading or progress bar works better.
- Avoid: Use more than 7 steps; consider grouping related steps or using a different pattern.
Theming API
{
"targets": [
{
"className": "astryx-stepper",
"visualProps": [
"orientation",
"indicatorPosition"
]
},
{
"className": "astryx-stepper-frame"
},
{
"className": "astryx-stepper-summary"
},
{
"className": "astryx-step",
"visualProps": [
"progress",
"status"
]
},
{
"className": "astryx-step-indicator",
"visualProps": [
"progress",
"status"
]
},
{
"className": "astryx-step-label",
"visualProps": [
"progress",
"status",
"disabled"
]
},
{
"className": "astryx-step-description",
"visualProps": [
"progress",
"status"
]
},
{
"className": "astryx-step-bar"
},
{
"className": "astryx-step-connector"
}
],
"vars": [
{
"name": "--step-connector-gap",
"description": "Gap a connector leaves where it meets the indicator, spent on the side facing it. Applies to the on-track layouts, whose connector is drawn as one segment either side of the node; 0 leaves the track running unbroken through it.",
"default": "0px"
}
]
}
Stepper
Container component that manages step state and renders steps in horizontal or vertical orientation as an ordered list.
| Prop | Type | Default | Description |
|---|---|---|---|
| activeStep * | number | Zero-based index of the currently active step. Steps before this index are marked as completed. | |
| children * | ReactNode | Step elements to render in the stepper. | |
| orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout direction of the stepper. |
| onStepClick | (index: number) => void | Called when a step is clicked or a compact summary control is used. Enables non-linear navigation. All non-disabled steps become clickable until a horizontal Stepper collapses, when navigation moves to summary controls that skip disabled steps. | |
| label | string | 'Progress' (localized) | Accessible label describing the set of steps (applied to the ordered list). Defaults to a localized "Progress". |
| density | 'compact' | 'balanced' | 'spacious' | 'balanced' | Controls the padding of all steps. |
| indicatorPosition | 'separated' | 'on-track' | 'separated' | Position of step indicators relative to the connector track. |
| horizontalOptions | { minimumStepWidth: number; collapsedVariant: 'withLabelAndControls' | 'withLabel' | 'hiddenLabel' } | { minimumStepWidth: 112, collapsedVariant: 'withLabelAndControls' } | Options for horizontal collapse. minimumStepWidth is the per-step threshold in pixels. collapsedVariant selects a label with controls, the label alone, or a bare progress track with no compact row. Controls appear only for withLabelAndControls when onStepClick is set, and every step keeps its accessible name. |
| xstyle | StyleXStyles | StyleX styles for layout customization. Must be a stylex.create() value. |
Step
Anatomy
- Stepper: The ordered list holding the steps. Owns the orientation and the indicator placement the whole flow is laid out on.
- Frame: The layout frame that groups the ordered steps with the optional compact summary shown at narrow widths.
- Compact summary: The optional row a horizontal Stepper adds directly beneath the track once it is too narrow to label every step. horizontalOptions.collapsedVariant chooses a label with Previous/Next controls, the label alone, or no row for a bare progress track. The on-track layout keeps its indicators on the rail instead of repeating the active indicator beside the label. Every step keeps its name in the accessible sequence at any width.
- Step: One step in the flow, and the element carrying its status. Wraps the indicator, label, description, and the track segments belonging to it.
- Progress bar: A 4px segmented bar per step. Filled for completed and active steps. Advancing one step grows the fill along the track it just covered, so the movement reads as progress rather than a bar changing color. Every other change applies at once: going back, jumping forward by more than one step, mounting mid-flow, and any change at all under prefers-reduced-motion. Where a span is drawn by more than one segment (the on-track layouts split it between two steps, three when a content slot sits between them), the segments run in track order at one constant speed, so the fill reads as a single line growing rather than pieces lighting in turn.
- Connector: The track drawn between indicators in the on-track layouts. Each connector paints an unfilled line and, over it, the accent fill covering the progress made. How many pieces a connector is drawn from is an implementation detail of the layout, not a themeable part; use --step-connector-gap to hold the track off the indicator.
- Indicator: A numbered badge, a check, or any custom icon. Controlled via the indicator prop.
- Label: Text identifying the step.
- Description: Supporting text below the label with additional context.
Examples
- Stepper — Checkout Progress: The default stepper: a horizontal track where every step owns an equal segment of the progress bar above its label. The default auto indicator resolves itself per step: a check once the step is done, a ring on the current step, a number for the ones still ahead. Click any step to jump.
- Step: A single Step, with every part it can render: the indicator, the label with its optional marker and trailing endContent, and the description beneath. A Step never sets its own completed/current state. It declares its index and derives the rest from the parent Stepper, so one Step in one Stepper is a complete example.
- Step — Content Slot: Children passed to a Step render below its description, indented to line up with the label rather than the indicator, and stay outside the clickable label area so buttons inside remain their own targets. In a full flow you gate the slot on the step being active. That is what turns a vertical stepper into an expanding one.
- Step — Indicator: Everything the indicator prop accepts: the auto default, an always-number badge, a custom ReactNode, and none, each on its own completed Step so the prop is the only difference between them. Every variant occupies the same 16px box, so a step swapping its number for a check as it completes never shifts the label beside it. The last cell shows that a custom node can be live rather than static: a Spinner on a step that is in progress, shaded
inheritso it picks up the step's own tint like any other glyph. - Stepper — Custom Content: A vertical stepper where each step owns a slice of the page. The content slot takes any node (form fields, a summary panel, a banner), so a stepper is not limited to multi-step forms. Rendering the slot only for the active step is what makes the flow expand one step at a time.
- Stepper — Indicator Modes: The indicator prop side by side: none (the bar and label carry the progress on their own), auto (check when done, ring when current, number ahead), always-number, and a custom icon per step.
- Stepper — On-Track Horizontal: The on-track layout in horizontal orientation: instead of sitting beside the label, each indicator is slotted into the connector itself, so the numbered nodes read as beads on one continuous line. Labels center under their node.
- Stepper — On-Track Vertical: The on-track layout in vertical orientation: indicators sit inline on a continuous connector rail, with each label and description beside its node. Compare with the separated layout, where the indicator sits alongside its own bar segment instead of on a shared rail.
- Stepper — Validation Status: Semantic status per step in a verification flow: success shows a green check, error a red glyph, accent the in-progress step. Status sets the indicator color and glyph only, never the connector, and is announced to assistive tech as text.
- Stepper — Horizontal Narrow Collapsed: A narrow horizontal stepper using horizontalOptions. At 320px wide, four steps fall below the 112px minimumStepWidth and collapse into a current-step summary with Previous and Next controls.
- Step — States: Every state a single Step can land in, each shown as one Step in its own Stepper. Completed, current, and upcoming are derived by comparing the step index against the parent activeStep, so they are never set directly; isDisabled and status are the two a step declares itself. Status is a separate axis from progress, which is why a completed step can still carry a warning.