Skip to content

Wizard

Multi-step forms with per-step validation, navigation, progress tracking, and optional persistence.

Import

ts
import {
  createWizard,
  useWizard,
  WizardProvider,
  useWizardContext,
  WizardStep,
  WizardProgress,
  WizardNavigation,
} from '@ereo/forms'

createWizard

Creates a wizard instance outside of React. Useful for programmatic control or non-React usage.

Signature

ts
function createWizard<T extends Record<string, any>>(
  config: WizardConfig<T>
): WizardHelpers<T>

WizardConfig

NameTypeDescription
stepsWizardStepConfig[]Array of step definitions (required)
formFormConfig<T>Form configuration (passed to FormStore) (required)
persist'localStorage' | 'sessionStorage' | falsePersist wizard state across page reloads
persistKeystringStorage key (default 'ereo-wizard')
onCompleteSubmitHandler<T>Called on final submit

WizardStepConfig

NameTypeDescription
idstringUnique step identifier (required)
fieldsstring[]Field paths that belong to this step (validated on next())
validate() => Promise<boolean> | booleanCustom step-level validation

WizardHelpers

NameTypeDescription
formFormStore<T>The form store instance
currentStepSignal<number>Current step index signal
completedStepsSignal<Set<string>>Set of completed step IDs
stateWizardStateCurrent state snapshot (getter)
next() => Promise<boolean>Validate current step and advance; returns false if validation fails
prev() => voidGo to previous step
goTo(stepIdOrIndex: string | number) => voidJump to a step by ID or index
submit() => Promise<void>Validate current step and submit the form
reset() => voidReset form, step, and completed state. Clears persisted data.
dispose() => voidClean up subscriptions and timers
getStepConfig(index: number) => WizardStepConfig | undefinedGet config for a step
canGoNext() => booleanWhether there is a next step
canGoPrev() => booleanWhether there is a previous step

WizardState

NameTypeDescription
currentStepnumberZero-based step index
currentStepIdstringID of the current step
completedStepsSet<string>Completed step IDs
totalStepsnumberTotal number of steps
isFirstbooleanWhether on the first step
isLastbooleanWhether on the last step
progressnumber0-1 progress ratio

useWizard

React hook that creates and manages a wizard. Same as createWizard but with React lifecycle management.

Signature

ts
function useWizard<T extends Record<string, any>>(
  config: WizardConfig<T>
): WizardHelpers<T> & { currentStepState: WizardState }

Returns the same WizardHelpers plus a reactive currentStepState that triggers re-renders when the step or completed steps change. The wizard is created once via useRef and disposed on unmount.

Components

WizardProvider

Provides wizard context to child components.

ts
function WizardProvider<T extends Record<string, any>>(props: {
  wizard: WizardHelpers<T>;
  children: ReactNode;
}): ReactElement

useWizardContext

ts
function useWizardContext<T>(): WizardHelpers<T> | null

Retrieves the wizard from context. Returns null if no WizardProvider is present.

WizardStep

Renders its children only when the step is active.

PropTypeDescription
idstringStep ID (must match a WizardStepConfig.id)
wizardWizardHelpersWizard instance (optional if inside WizardProvider)
keepMountedbooleanKeep in DOM when inactive (hidden with display: none). Default false.
childrenReactNodeStep content

Renders a <div> with role="tabpanel" and aria-hidden for accessibility.

WizardProgress

Renders a step indicator with active/completed states.

PropTypeDescription
wizardWizardHelpersWizard instance (optional if inside WizardProvider)
renderStep(step, index, { isActive, isCompleted }) => ReactNodeCustom step renderer

Default rendering produces a div[role="tablist"] with div[role="tab"] children.

WizardNavigation

Renders Back / Next / Submit buttons based on the current step.

PropTypeDefaultDescription
wizardWizardHelperscontextWizard instance (optional if inside WizardProvider)
backLabelstring'Back'Back button text
nextLabelstring'Next'Next button text
submitLabelstring'Submit'Submit button text (shown on last step)

Example: 3-Step Registration

tsx
import {
  useWizard,
  useField,
  WizardProvider,
  WizardStep,
  WizardProgress,
  WizardNavigation,
  Field,
  required,
  email,
  minLength,
} from '@ereo/forms'

function RegistrationWizard() {
  const wizard = useWizard({
    steps: [
      { id: 'account', fields: ['email', 'password'] },
      { id: 'profile', fields: ['name', 'bio'] },
      { id: 'confirm' },
    ],
    form: {
      defaultValues: { email: '', password: '', name: '', bio: '' },
      validators: {
        email: [required(), email()],
        password: [required(), minLength(8)],
        name: [required()],
      },
    },
    persist: 'localStorage',
    onComplete: async (values) => {
      await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify(values),
      })
    },
  })

  return (
    <WizardProvider wizard={wizard}>
      <WizardProgress />

      <WizardStep id="account">
        <Field name="email" label="Email" />
        <Field name="password" label="Password" type="password" />
      </WizardStep>

      <WizardStep id="profile">
        <Field name="name" label="Full Name" />
        <Field name="bio" label="Bio" />
      </WizardStep>

      <WizardStep id="confirm">
        <p>Review your details and submit.</p>
      </WizardStep>

      <WizardNavigation />
    </WizardProvider>
  )
}

Persistence

When persist is set, the wizard auto-saves to localStorage or sessionStorage:

  • Saves after any form value change, step change, or completed steps change (debounced 300ms)
  • Restores values, step, and completed steps on mount
  • Clears storage after successful submit or reset()
  • useForm -- single-step form
  • FormStore -- underlying form instance
  • Context -- FormProvider for the form, WizardProvider for the wizard
  • Validation -- per-step field validation

Released under the MIT License.