Wizard
A multi-step flow the reader works through: configure the instance, connect the database, invite the team, review, go.
Component behavior
Wizard renders a multi-step flow with validation, conditional steps, completion state, and ordered navigation. Validation can be asynchronous. Revisiting an earlier step preserves later completion state.
- First-run license, instance, database, and identity configuration.
- Create or import tasks that require multiple screens.
- Conditional steps based on earlier answers.
Stepper: only step progress is required.
Tour: steps explain an existing interface.
SettingsModal: sections are independent and unordered.
Form + Field: the task fits on one form screen.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps | WizardStep<T>[] | — | The flow. Written inline is fine. |
activeStep | string | — | Controlled step id. Pair with onStepChange. |
defaultStep | string | — | Where an uncontrolled wizard starts, and where reset returns to. |
onStepChange | (id, step, meta) => void | — | Fires on every move. meta carries the index and the direction. |
values | T | — | Controlled answers. Pair with onValuesChange. |
defaultValues | T | — | Initial values for uncontrolled state. |
onValuesChange | (values: T) => void | — | Fires on every setValue / setValues. |
completed | string[] | — | Controlled completion state. |
defaultCompleted | string[] | — | Steps already passed at first render. |
onCompletedChange | (ids: string[]) => void | — | Fires whenever a step is marked done or undone. |
enforceStepOrder | boolean | true | Steps must be passed in order. Off, every step is reachable at once. |
validateOnNext | boolean | true | Run the step's validate on the way out. |
onFinish | (values: T) => void | Promise<void> | — | The reader finished. A promise holds submitting until it settles; a rejection becomes the last step's error. |
onCancel | () => void | — | Adds the Cancel button. Without it there is none. |
onValidationError | (id, message) => void | — | Fires whenever a step fails, with the message the reader will see. |
layout | "rail" | "steps" | "bare" | "rail" | Rail beside the pane, band above it, or neither. |
title | ReactNode | — | Heading above the step navigation. |
subtitle | ReactNode | — | Secondary text below the title. |
brand | ReactNode | — | Logo mark beside the title. |
headerActions | ReactNode | — | Right-aligned slot in the pane header. |
navFooter | ReactNode | — | Content at the bottom of the step navigation. |
footerContent | ReactNode | — | Extra content on the left of the footer row. |
progressDisplay | "bar" | "count" | "both" | "none" | "both" | Whether the reader is told where they are with a bar, a count, or both. |
navWidth | number | 264 | Rail width in px. |
contentWidth | number | — | Maximum width of the step content, in pixels. |
contentAlign | "start" | "center" | — | Defaults to centered in the band layout, left-aligned in the rail. |
bordered | boolean | true | Whether to render a bordered frame around the flow. |
elevation | SurfaceElevation | "flat" | Surface elevation for the frame. Use "dialog" for a centered card. |
showStepHeader | boolean | true | The step's title and description above its body. |
showBackButton | boolean | true | Back button in the footer, hidden on the first step. |
showCancelButton | boolean | true | Shows the Cancel button when onCancel is set. |
showSkipButton | boolean | true | Skip button on steps marked optional. |
keyboardShortcuts | boolean | true | ⌘ / Ctrl + Enter advances. |
focusOnStepChange | boolean | true | Moves focus to the pane heading on every step change, never on first render. |
labels | Partial<WizardLabels> | — | Overrides the button and counter copy. |
renderNav | (api) => ReactNode | — | Replaces the rail contents or the band. |
renderHeader | (api) => ReactNode | — | Replaces the pane header. |
renderContent | (api) => ReactNode | — | Replaces the step body, error strip included. |
renderFooter | (api) => ReactNode | — | Replaces the action row. |
children | ReactNode | — | Rendered under the step body, inside the scrolling pane. |
Steps
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Stable id. What activeStep, goTo and onStepChange speak in. |
label | ReactNode | — | Nav label, and the pane title unless title overrides it. |
title | ReactNode | — | Pane title, when it should differ from the nav label. |
description | ReactNode | — | Dim line under the pane title. |
hint | ReactNode | — | Dim second line under the nav label. |
icon | ReactNode | — | Leading nav glyph, in place of the step number. |
group | string | — | Uppercase heading above this item in the rail. Consecutive steps sharing one sit under it. |
badge | ReactNode | — | Content after the step label, such as a count or status indicator. |
optional | boolean | — | The footer grows a Skip button and the nav says so. |
content | ReactNode | ((api) => ReactNode) | — | The step's body. A function receives the live api. |
validate | (values, api) => WizardValidation | Promise<…> | — | Runs before leaving forwards. false or a message keeps the reader here. |
includeWhen | (values: T) => boolean | — | Includes the step only when the predicate returns true. |
canAdvance | boolean | — | Hard gate on Next, for a step whose control is the validation. |
disabled | boolean | — | Not reachable from the nav, even when everything before it is done. |
nextLabel | ReactNode | — | Replaces the advance button's label on this step. |
backLabel | ReactNode | — | Replaces Back on this step. |
actions | ReactNode | — | Replaces this step's footer action row. |
hideFooter | boolean | — | Hides the footer for this step. |
onEnter | (api) => void | — | Called when the step is entered. Use to reveal a target inside a closed container. |
onLeave | (api) => void | — | Runs as the step is left, in either direction. |
Labels
| Prop | Type | Default | Description |
|---|---|---|---|
next | ReactNode | "Continue" | Advance button. |
back | ReactNode | "Back" | Previous button. |
finish | ReactNode | "Finish setup" | Advance button on the last step. |
cancel | ReactNode | "Cancel" | Leave-entirely button, shown only with onCancel. |
skip | ReactNode | "Skip" | Skip button on an optional step. |
optional | ReactNode | "Optional" | Nav marker on a step the reader may pass. |
skipped | ReactNode | "Skipped" | Nav marker on an optional step they did pass. |
stepCount | (index, total) => ReactNode | "Step 2 of 6" | The counter line. |
progress | (percent: number) => ReactNode | "40% complete" | The line beside the progress bar. |
nav | string | "Setup steps" | Accessible name of the nav landmark. |
status | (status: WizardStepStatus) => string | — | Accessible state text on each nav item. |
The pieces
WizardNav renders a Stepper using wizard steps, statuses, and reachability. Remaining StepperProps are forwarded except for props managed by the wizard.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "vertical" | "horizontal" | "vertical" | Rail or band. |
variant | "circles" | "bars" | "dots" | "panels" | "panels" vertical, "circles" horizontal | Stepper's drawing. A rail is navigation, so its steps default to pressable tiles; a band above the pane is the numbered strip. |
size | "sm" | "md" | "lg" | "md" | Sets marker size, font size, and spacing for the step navigation. |
tone | ToneProp | "primary" | Accent for done and current markers and the filled run of line. |
labelPlacement | "inline" | "below" | "none" | "inline" | Passed to the stepper. below only makes sense on a horizontal band. |
showConnectors | boolean | true | The line between step markers. It breaks at every group heading, so a grouped rail reads as blocks. |
showNumbers | boolean | true | Numbers in the markers of steps that are neither done, errored nor skipped. |
showDetails | boolean | true | Shows step hints and Optional or Skipped labels. When false, displays step labels only. |
fill | boolean | true | Distributes horizontal steps evenly. Below-label connectors require equal-width columns. |
as | ElementType | "nav" | The root element. The nav is a landmark named by labels.nav. |
renderItem | (step: WizardStep, state) => ReactNode | — | Custom step content receiving the step and index, status, reachable, and select values. Retains navigation buttons and state styling. |
renderMarker | (step: Step, state: StepState) => ReactNode | — | Replaces the marker and retains the label column. Receives the mapped Stepper step. |
| Prop | Type | Default | Description |
|---|---|---|---|
showBackButton | boolean | true | Back button, hidden on the first step. |
showCancelButton | boolean | true | Cancel button, only when the wizard was given an onCancel. |
showSkipButton | boolean | true | Skip button, only on steps marked optional. |
children | ReactNode | — | Extra content on the left of the row. |
| Prop | Type | Default | Description |
|---|---|---|---|
items | WizardSummaryItem[] | — | { label, value, stepId?, mono?, group? } per row. A row's group defaults to the group of the step named by stepId, so the review falls under the same headings the rail used. |
editLabel | ReactNode | "Edit" | The jump-back link's label. |
columns | 1 | 2 | 1 | Lays the groups out side by side once there is room. |
flat | boolean | — | Ignores group and renders one flat list. |
Steps, values and branches
steps is required. Each step has an id and label. content accepts a node or a function receiving the current wizard API.
const steps: WizardStep<Setup>[] = [
{
id: "instance",
label: "Instance",
group: "Get started", // uppercase heading in the rail
hint: "Name and public URL", // second line under the label
description: "Instance identification settings.",
validate: (values) =>
values.url.startsWith("https://") || "The base URL has to be https://.",
content: (api) => (
<Field label="Base URL" htmlFor="url">
<Input
id="url"
value={api.values.url}
onChange={(e) => api.setValue("url", e.currentTarget.value)}
/>
</Field>
),
},
];Use defaultValues for internal answer state or values with onValuesChange for controlled state. setValue and setValues update either form.
Branches
includeWhen controls whether a step is included. Excluded steps do not appear, count toward progress, or block navigation. If the active step becomes excluded, selection moves to the nearest preceding visible step.
{
id: "connection",
label: "Connection",
includeWhen: (values) => values.engine !== "bundled",
validate: (values) => values.tested || "Run Test connection first.",
content: (api) => …,
}Validation
validate returns true or undefined for success, false for failure without a message, or a string for failure with a message. Promises show a spinner until settled. Thrown errors fail validation. Error messages appear above the step and mark it as failed in navigation.
Examples
A setup flow
Seven steps, three groups, one optional, two that come and go. Pick Bundled SQLite and the Connection step leaves the flow; pick Local accounts and the Provider step does.
A short flow
layout="steps" places step navigation above the content. The default layout places navigation beside the content.
Custom layout
useWizard provides the state machine without layout. WizardProvider exposes it to WizardNav, WizardFooter, and WizardSummary.
Keyboard & accessibility
- The rail is a
navof buttons; the current step carriesaria-current="step"and every item has visually-hidden state text (“Completed”, “Needs attention”). - Unreachable steps are disabled and excluded from Tab navigation.
- Focus moves to the pane heading on every step change, never on first render.
⌘/Ctrl+Enteradvances.- Each navigation item exposes
data-state:current,complete,skipped,error, orupcoming.
Import
import {
Wizard,
WizardNav,
WizardFooter,
WizardSummary,
WizardProvider,
useWizard,
} from "@solarius/onset";
import type { WizardStep, WizardApi } from "@solarius/onset";
<Wizard
steps={steps}
defaultValues={{ url: "", engine: "postgres" }}
title="Set up Anchorpoint"
onFinish={install}
/>;