Skip to content

Components

React Multi-Step Forms — Validate the Step, Not the Keystroke

Per-Step Zod Schemas, One Form State, and Why onBlur Beats onChange Every Time
Written by Aiden Wood Founder and Lead Design Engineer
🧙 ✅ 3️⃣

Multi-step forms convert better than long ones for exactly one reason: each screen looks finishable. Then half the implementations throw the advantage away by yelling "invalid email" at the second keystroke, or by losing step 2's data when the user goes back from step 3.

Both failures are the same two design decisions made wrong — when to validate, and where state lives. Velocity X's multi-step-wizard-form (with standard-wizard-stepper-nav for the step rail) makes them right, and the pattern is worth stealing even if you build your own.

The Money Pattern

One form state for the whole wizard, one Zod schema per step, and advancing is just validating the current slice:

const steps = [
  { fields: ['name', 'email'], schema: contactSchema },
  { fields: ['company', 'size'], schema: companySchema },
  { fields: ['plan'], schema: planSchema },
] as const;

const next = async () => {
  const valid = await trigger(steps[step].fields); // RHF validates ONE step
  if (valid) setStep(s => s + 1);
};

React Hook Form holds every field across all steps — going back is free, nothing unmounts into the void (keep inactive steps mounted and hidden, or use RHF's shouldUnregister: false). The full schema is just contactSchema.merge(companySchema).merge(planSchema), shared with the server so the API rejects exactly what the UI rejects.

The Catch

Validation timing is a courtesy rule, not a technical one: validate a field on blur, re-validate on change only after it has failed once. Instant onChange validation scolds people mid-word; submit-only validation ambushes them at the end. RHF's mode: 'onTouched' is this exact behaviour for free.

And the stepper must tell the truth. A step rail that shows 3 ticks when step 2 silently failed server-side validation is worse than no rail — mark steps complete only from validated data, never from "the user clicked next".

The Verdict

A wizard is one form wearing three screens, not three forms holding hands. Single state, per-step schemas, onTouched timing — that's the entire trick. The assembled version lives in Velocity X's Forms category, 29 components, installed as source you own.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.