easy-forms
Form components

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++.

Preview
Live
Pro

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:

ValueBehaviour
omittedLinear — the next step in array order, or terminal if last
stringAlways 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.

PropTypeDefaultDescription
'strict'navigationdefaultA step with errors blocks Next. The classic wizard.
'lenient'navigationNext 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

PropTypeDefaultDescription
storeFormStoreThe store this wizard created. Provide it via FormStoreProvider.
stepsAdvancedWizardStepState[]Every configured step decorated with navigation state.
pathAdvancedWizardStepState[]completed + active + upcoming — what a progress indicator renders.
currentAdvancedWizardStepStateThe active step's state.
mountedStepIdsstring[]Step ids whose panels should mount. Off-path steps stay unmounted.
invalidStepsAdvancedWizardStepState[]Path steps currently in error — for a final-stage summary.
isFirstStepbooleanTrue when the active step is the entry step.
canGoPreviousbooleanTrue when there is a step to pop back to.
canGoNextbooleanTrue 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() => voidPop 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.
isTerminalStepbooleanTrue 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