useWizard
Hooks

useWizard

The Wizard state machine without chrome: steps, values, validation, completion, and navigation.

Signature

useWizard provides wizard state and navigation without rendering a layout. Pass it to WizardProvider for WizardNav, WizardFooter, and WizardSummary, or connect it to custom controls.

SignatureTypeScript
function useWizard<T>(options: {
  steps: WizardStep<T>[];
  activeStep?: string; // controlled step id
  defaultStep?: string;
  onStepChange?: (id, step, meta: { index; direction }) => void;
  values?: T; // controlled answers
  defaultValues?: T;
  onValuesChange?: (values: T) => void;
  completed?: string[]; // controlled completion
  defaultCompleted?: string[];
  onCompletedChange?: (ids: string[]) => void;
  enforceStepOrder?: boolean; // default true
  validateOnNext?: boolean; // default true
  onFinish?: (values: T) => void | Promise<void>;
  onCancel?: () => void;
  onValidationError?: (id: string, message: ReactNode) => void;
}): WizardApi<T>;

Return value

WizardApi props
PropTypeDefaultDescription
stepsWizardStep<T>[]—The steps in play, with includeWhen already applied.
allStepsWizardStep<T>[]—Every step handed to the wizard, hidden ones included.
stepWizardStep<T>—The step being shown.
indexnumber—Its position among the visible steps, zero-based.
totalnumber—How many steps are in play.
isFirstboolean—True on the first step.
isLastboolean—True on the last step.
direction"forward" | "back" | "jump"—Which way the last move went; drives the content transition.
progressnumber—0–100 by completed steps. What the progress bar draws.
valuesT—The answers so far.
setValues(patch | updater) => void—Merges a patch, or replaces via an updater.
setValue(key, value) => void—One key, for onChange handlers.
completedstring[]—Ids of steps that have been passed.
skippedstring[]—Ids of optional steps that were skipped.
errorsRecord<string, ReactNode>—Current message per step id.
status(id) => WizardStepStatus—current | complete | skipped | error | upcoming, for one step.
validatingboolean—A step's validate is running.
submittingboolean—onFinish is in flight.
canGoTo(id: string) => boolean—Whether the nav should let the reader jump there.
canAdvanceboolean—True when the step permits advancement and no validation or submission is pending.
next() => Promise<boolean>—Validates, marks the step complete, and moves on, or finishes on the last step.
back() => void—Steps back. Never validates.
goTo(target, options?) => Promise<boolean>—By id or by index among the visible steps. Refused when the step is not reachable.
skip() => void—Moves past an optional step without validating it.
finish() => Promise<boolean>—Runs onFinish, with submitting for as long as it takes.
reset() => void—Back to the first step, with the starting values.
markComplete(id: string) => void—Marks a step passed without visiting it.
markIncomplete(id: string) => void—Un-marks one; a later answer invalidated an earlier step.
setError(id, message | null) => void—Sets or clears a step's message from outside its validator.
onCancel(() => void) | undefined—Controls whether WizardFooter displays Cancel.

Usage

Provider components

Custom chromeTSX
const wizard = useWizard({ steps, defaultValues: { region: "eu" } });

<WizardProvider value={wizard}>
  <Card>
    <CardHeader sub={`${wizard.index + 1} / ${wizard.total}`}>
      Attach a cluster
    </CardHeader>
    <Box px="3" py="2" borderBottom bg="bg-deep">
      <WizardNav orientation="horizontal" showDetails={false} />
    </Box>
    <CardBody>
      {typeof wizard.step.content === "function"
        ? wizard.step.content(wizard)
        : wizard.step.content}
    </CardBody>
    <WizardFooter />
  </Card>
</WizardProvider>;

Custom controls

WizardProvider is optional when using the API directly. next still validates and goTo still checks reachability.

BareTSX
<Button
  variant="primary"
  loading={wizard.validating || wizard.submitting}
  disabled={!wizard.canAdvance}
  onClick={() => void wizard.next()}
>
  {wizard.isLast ? "Install" : "Continue"}
</Button>

Resuming

A self-hosted setup that survives a container restart keeps values and completed outside the hook. Both are controllable, so persisting a flow takes two props and a defaultStep.

Resumed from storageTSX
const saved = JSON.parse(localStorage.getItem("setup") ?? "{}");

const wizard = useWizard({
  steps,
  values: config,
  onValuesChange: (next) => {
    setConfig(next);
    persist(next);
  },
  defaultCompleted: saved.completed,
  defaultStep: saved.step,
  onStepChange: (id) => persistStep(id),
});