Advanced wizard
Branching multi-step flows where the next step is computed from answers.
The free wizard walks a flat step array in order. The Pro wizard adds declarative
per-step routing, so the taken path is a graph walk rather than index++.
The live preview for this Pro component is not configured in this environment. The Code tab shows the full schema, and Easy Forms Pro covers what it renders.
About the badge in the corner
The preview above is unlicensed, so @easy-forms/pro renders its watermark badge. Setting a
license key removes it — see Licensing.
const wizard = useAdvancedWizard(config, { onSubmit, initialValues, dependencyHandlers, plugins });The hook creates its own store. Provide it so the step fields register into it:
<FormStoreProvider store={wizard.store}>{/* step panels */}</FormStoreProvider>Configuring routes
const config: AdvancedWizardConfig = {
steps: [
{
id: 'account',
title: 'Account type',
groups: [/* … */],
next: [
{ fieldNames: ['accountType'], when: (v) => v.accountType === 'business', to: 'company' },
{ fieldNames: [], when: () => true, to: 'personal' },
],
},
{ id: 'company', title: 'Company details', groups: [/* … */], next: 'review' },
{ id: 'personal', title: 'Your details', groups: [/* … */], next: 'review' },
{ id: 'review', title: 'Review', groups: [/* … */], terminal: true },
],
};next accepts three shapes:
| Value | Behaviour |
|---|---|
| omitted | Linear — the next step in array order, or terminal if last |
string | Always advance to that step id |
AdvancedWizardRoute[] | First route whose when returns true wins. A trailing when: () => true acts as the else. If none match, the step is terminal |
fieldNames is the subscription surface — the values passed to when — mirroring how
propsDependsOn works.
Navigation modes
| Prop | Type | Default | Description |
|---|---|---|---|
| 'strict' | navigation | default | A step with errors blocks Next. The classic wizard. |
| 'lenient' | navigation | — | Next always advances; errors are still computed and surfaced per step, the whole projected path is jumpable, and submit() still validates and blocks so every error surfaces at the last stage. |
What the hook returns
| Prop | Type | Default | Description |
|---|---|---|---|
| store | FormStore | — | The store this wizard created. Provide it via FormStoreProvider. |
| steps | AdvancedWizardStepState[] | — | Every configured step decorated with navigation state. |
| path | AdvancedWizardStepState[] | — | completed + active + upcoming — what a progress indicator renders. |
| current | AdvancedWizardStepState | — | The active step's state. |
| mountedStepIds | string[] | — | Step ids whose panels should mount. Off-path steps stay unmounted. |
| invalidSteps | AdvancedWizardStepState[] | — | Path steps currently in error — for a final-stage summary. |
| isFirstStep | boolean | — | True when the active step is the entry step. |
| canGoPrevious | boolean | — | True when there is a step to pop back to. |
| canGoNext | boolean | — | True when a forward route resolves from the current answers. |
| goNext | () => Promise<boolean> | — | Validate (if gated) then advance along the resolved route. Resolves false if blocked or terminal. |
| goPrevious | () => void | — | Pop back one step. No validation. |
| goTo | (stepId: string) => Promise<boolean> | — | Jump to a navigable step, truncating the stack. |
| submit | () => Promise<void> | — | Validate the terminal step then submit through the store. This is what a terminal step's Submit button calls. |
| isTerminalStep | boolean | — | True when the active step should show Submit rather than Next. |
| progress | { current: number; total: number } | — | Position along the projected path — for a progress bar or "step 2 of 5" label. |
Each step state carries a status of active, completed, upcoming, or unreachable
(a not-taken branch), plus errorCount / hasErrors for error chips.
Rerouting is automatic
The back-stack is always exactly start → current. goNext re-resolves the route from the
live answers each time, so going back and changing an earlier answer naturally reroutes and
discards a previously-taken forward branch.
Abandoned branches lose their answers
Only steps on the projected path are mounted (mountedStepIds), so off-path fields never
register with the store. If a user answers several steps down one branch, goes back, and
reroutes, those answers are gone — not hidden.
This is deliberately different from the free wizard, which keeps every step mounted and
CSS-hides the inactive ones precisely to preserve state. If you need answers to survive a
reroute, capture them yourself in onChange before the branch changes.
Persistence is yours
Unlike the free wizard's persistKey, the Pro wizard has no built-in persistence — save and
resume are the consumer's to own.
Getting the renderer
EASY_FORMS_PRO_TOKEN=<your token> npx shadcn@latest add @ef-pro/advanced-wizard