Skip to content

Wizard

Guide people through a task with step validation and an async finish action.

import { Wizard, WizardStep } 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 wizard when a task is long enough to split but each part depends on the one before: creating a workspace, a checkout, importing data. It draws a Stepper, shows one step's content, and handles Back, Continue, Skip and Finish. When every field fits on one screen, a single Form is quicker to fill in. When people need to compare the steps, don't hide them: lay them out as sections of one page.

import { Wizard, WizardStep } from '@youj/react'

export function CreateProject() {
  return (
    <Wizard onComplete={() => createProject(values)} finishLabel="Create project">
      <WizardStep id="name" title="Name" validate={() => values.name.length > 0}>
        <NameFields />
      </WizardStep>
      <WizardStep id="repo" title="Repository" optional>
        <RepoPicker />
      </WizardStep>
      <WizardStep id="review" title="Review">
        <Summary />
      </WizardStep>
    </Wizard>
  )
}

Each WizardStep is a step: its title goes in the stepper and heads the content, and its children show while it's current. The wizard keeps the step index itself, or pass step and onStepChange to control it. On the last step, Finish runs onComplete through the Button's onAction, so it shows a spinner and then a drawn check with finishSuccessLabel.

Reference

Propschildren, step, defaultStep… (18)

Props

Wizard props
PropType
childrenRequiredReactNode–
stepnumber

Controlled current step index.

–
defaultStepnumber

default 0

0
onStepChange(step: number) => void–
onComplete() => unknown | Promise<unknown>

Runs when Finish is pressed on the last step (after its validation). Resolve for success.

–
orientation'horizontal' | 'vertical'

default 'horizontal'

'horizontal'
backLabelstring

Labels.

–
nextLabelstring–
finishLabelstring–
finishSuccessLabelstring–
footerStartReactNode

Extra content in the footer, at the inline start (e.g. a Cancel button).

–
headingLevel2 | 3 | 4

The step title's heading level: 3 under a section or dialog title (the default), 2 straight under a page's h1.

default 3

3
Other props pass through: extends ComponentProps<'div'>.

WizardStep

WizardStep props
PropType
idRequiredstring–
titleRequiredReactNode–
descriptionReactNode–
optionalboolean

Can be skipped: shows "Optional" and a Skip button.

–
validate() => ValidationResult | Promise<ValidationResult>

Runs when leaving the step forwards. Return (or resolve) false to stay on it (the step's own field errors explain why), or a message, which is shown under the step and announced. Throwing counts as a failure with the error's message.

–
childrenRequiredReactNode–
MotionStep enter, step exit, panel height… · reduced motion

Motion

Motion tokens
MomentTokens
Step enterdrifts 24px in from the side you travel to and fades in
duration.baseease.standard

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

220mscubic-bezier(0.2, 0, 0, 1)
Step exitdrifts 24px out the other way as it fades
duration.baseease.standard

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

220mscubic-bezier(0.2, 0, 0, 1)
Panel heightglides between the two steps' heights
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Stepperthe connector fills or drains, see Stepper
duration.slowease.standard

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

340mscubic-bezier(0.2, 0, 0, 1)
Finishthe button's spinner, then a drawn check
duration.slowease.emphasized

340ms · cubic-bezier(0.6, 0, 0.1, 1)

340mscubic-bezier(0.6, 0, 0.1, 1)

Reduced motion

Steps swap in place and the panel takes its new height at once.

The drift follows the reading direction, so under RTL forwards comes in from the left. The outgoing step is popped out of the layout while it leaves, so rapid Back and Continue presses retarget the height without a jump.

Reduced motion

Steps replace each other in place with no drift or fade, and the panel height changes at once. Focus, announcements and validation are unchanged.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • "Step 2 of 4" above the content is a polite live region that also speaks the step's title, so each move is announced.
  • After Back or Continue, focus moves to the new step's heading, so a screen reader starts reading from the top of the step and a keyboard user tabs on into its fields.
  • A validation message has role="alert", and the failed step is spoken as "has an error" in the stepper.
  • Done steps in the stepper are buttons that go back to them. Later steps can only be reached by Continue, so validation can't be skipped.
  • Continue sets aria-busy while an async validation runs, and ignores presses until it settles.