VibeWeek
Home/Grow/Multi-Step Forms & Wizards

Multi-Step Forms & Wizards

⬅️ Day 6: Grow Overview

If you're building B2B SaaS in 2026, you need multi-step forms — onboarding flows, signup wizards, complex configuration, KYC flows, application forms, deal-room intake. The naive approach: one giant form with 30 fields. The structured approach: break it into 3-7 steps with progress indication, per-step validation, server-side draft persistence, the back-button working, and error recovery. This guide covers the implementation craft. (See form-validation-ux-chat.md for single-form validation patterns; this is about flows with multiple steps.)

1. Decide if you need a wizard at all

Most "wizards" are over-engineered. Single forms with smart defaults beat wizards for short interactions.

Decide whether to build a wizard.

Build a wizard when:
- 8+ fields total
- Fields naturally group into 3+ logical sections
- Order matters (step 2 depends on step 1's answers)
- High-stakes (signup, payment, KYC) where progressive disclosure reduces abandonment
- Branching logic (different paths based on answers)

Skip the wizard, use a single form when:
- <8 fields
- Fields are flat / non-hierarchical
- Users want to scan + jump
- Power users will hate clicking "Next" 5 times

Alternative: single page with grouped sections + sticky save button.
- Often beats wizard for B2B power users
- Headings act as section breaks
- Field-level autosave

For this flow [DESCRIBE]: wizard or single form?

Output:
1. Recommendation
2. Reasoning
3. If wizard: target step count (3-7)
4. If single form: section structure
5. Hybrid option (wizard for first-time, single page for return)

The pattern most-violated: 4 fields split into 4 steps "to feel quick." Each click is friction. Combine.

2. Plan the step graph

A wizard isn't always linear. Plan branching before coding.

Map the step graph for a multi-step form.

Linear flow (the 70% case):
- Step 1 → Step 2 → Step 3 → Done
- Each step always required
- Example: signup (account → company → plan)

Branching flow (the 25% case):
- Step 1 → (decision) → Step 2A or Step 2B → Step 3
- Branches based on user input
- Example: KYC (individual → personal-info; business → company-info)

Loop / repeat (the 5% case):
- Step 1 → Step 2 (per item, repeat) → Step 3
- Add multiple of something (team members, products)

For each step, define:
- Step ID (slug)
- Label (display name)
- Inputs (field names + types)
- Validation rules
- Branch logic (next step depends on what?)
- Skip logic (when can user skip?)
- Required vs optional

Diagram tools:
- Whiteboard / Excalidraw
- Mermaid diagram (commit to repo as docs)
- State machine (XState — for complex branching)

Output:
1. Step graph (mermaid or list)
2. Validation matrix per step
3. Branch decision points
4. State-machine model if non-trivial

The state-machine pattern: for any wizard with branches, model it as an XState chart. Forces explicit transitions; debug visually. Worth the 30-min learning investment.

3. URL state — each step is a URL

A wizard that loses state on browser back is broken. URL-state per step fixes most issues.

Implement URL state for wizard steps.

Pattern: each step is a URL.
- /onboarding/account
- /onboarding/company
- /onboarding/plan
- /onboarding/done

Browser back: navigates to previous step
Browser forward: re-navigates if data still there
Refresh: lands on current step

URL stores:
- Current step (slug in path)
- Optional: form data (for resumable flows; consider security)

Stack: Next.js 16 App Router.
- Each step = page (e.g., app/onboarding/[step]/page.tsx)
- OR single page with internal step state + URL search param

Server-side considerations:
- Server-render the step from URL
- Pre-populate fields from saved data
- Validate user can access this step (e.g., haven't skipped step 1)

Edge cases:
- Direct URL to step 3 (user bookmarked)
  - If prior steps incomplete: redirect to first incomplete
  - If complete: show step 3 with prior data
- Refresh mid-step
  - Restore field values from saved draft
  - Don't lose entered-but-unsaved data

Output:
1. Route structure (Next.js 16 example)
2. Server-side step-access guard
3. Draft-state restoration on refresh
4. Browser back/forward behavior
5. Direct-link handling

The browser-back rule: every wizard breaks on browser back. Test it explicitly. If users hit browser back and lose state, your funnel leaks.

4. Server-side draft persistence

Long wizards (KYC, application forms) need draft-saving so users can return tomorrow.

Implement draft persistence.

Storage:
- Database table: form_drafts (id, user_id, form_type, current_step, data_json, updated_at)
- Or per-flow tables (kyc_drafts, signup_drafts) if domain warrants

Save trigger:
- Auto-save on field blur (300ms debounce)
- Auto-save on step navigation (next/back)
- Manual save button (optional; reassures users)

Restoration:
- On page load: query latest draft for user + form_type
- Pre-populate fields
- Resume at last step

Expiration:
- Drafts older than 30 days auto-deleted
- Don't keep PII / sensitive data forever

Security:
- Only owner can read their draft
- Some data shouldn't be drafted (passwords, payment info)

UX:
- "Draft saved" indicator (subtle; not toast)
- "Continue where you left off" CTA on entry
- Warn before discarding draft

Output:
1. Database schema for drafts
2. API endpoints (GET, PUT, DELETE draft)
3. Auto-save hook (debounced)
4. Restoration on page load
5. Expiration job (daily cleanup)
6. Security model (RBAC)

The auto-save subtlety: debounce field-blur saves at 300ms. Too aggressive and you spam the API; too lazy and refreshes lose 30 seconds of work.

5. Per-step validation

Validate as users go, not all at once at the end.

Per-step validation strategy.

Validation runs at:
- Field blur (immediate feedback on bad input)
- Step navigation attempt (blocks "Next" if invalid)
- NOT on every keystroke (too noisy)

Validation library:
- Zod (TypeScript-first; schema-based; recommended in 2026)
- Yup (older alternative)
- React Hook Form built-in

Step-level schema:
- Each step has a Zod schema
- Validate on "Next" click; only proceed if valid
- Show errors inline at fields, not as toast

Server-side validation:
- Re-validate on save / step transition (server is truth)
- Don't trust client validation alone
- Return field-level errors in API response

Cross-step validation:
- Some rules span steps (e.g., billing address + shipping address comparison)
- Validate at submission of final step
- Or validate when both fields present

Async validation:
- Email uniqueness check (debounced API call)
- Coupon code validation
- Show pending state during async check

Output:
1. Zod schemas per step
2. Field-level error display
3. Step-transition validation
4. Async-validation pattern (debounced + spinner)
5. Cross-step validation hook
6. Server-side mirror validation

The error-display rule: errors appear at the field, in red, with specific text ("Email already in use"). Toast-only errors lose context; users scroll back hunting for the broken field.

6. Progress indication

Users want to know how far they are. Make it visible.

Design wizard progress UI.

Three patterns:

Pattern A: Stepper (linear)
- Horizontal line with circles for each step
- Current step highlighted; past steps checkmarked
- Click past steps to go back (if allowed)
- Best for: 3-7 steps

Pattern B: Progress bar
- Horizontal bar filling 0-100%
- Less informative but cleaner
- Best for: many steps (>7) or when each step is short

Pattern C: Numbered list (sidebar)
- Vertical list with current step highlighted
- Used in long forms / settings wizards
- Best for: 5-15 steps

Anti-pattern: no progress indication
- User has no sense of remaining effort
- Abandonment higher

Click-to-jump:
- Allow clicking past steps to revisit
- Don't allow clicking future steps (incomplete data)
- Visual: past = filled / clickable; current = highlighted; future = grayed

Mobile:
- Horizontal stepper takes too much room
- Use compact: "Step 2 of 5" + progress bar
- Or vertical sidebar that slides in

Output:
1. Recommended pattern for [STEP COUNT]
2. Component (Tailwind / shadcn)
3. Click-to-revisit behavior
4. Mobile fallback
5. Accessibility: aria-current="step" on active

The 3-step rule: if you have 3-5 steps, show the full stepper. If 6+, switch to compact "Step 4 of 8" + bar. Steppers crowded with circles look junior.

7. Navigation — back button, skip, save & exit

Wizard navigation controls.

Required:
- Next button (primary, right-aligned)
- Back button (secondary, left-aligned)
- Either button shows loading state during async actions

Optional:
- Skip button (when step is genuinely optional)
- Save & exit (return later; saves draft + redirects)
- Exit without saving (with confirm modal)

Behaviors:
- Next: validate current step, then navigate
- Back: navigate without re-validating (data already saved)
- Skip: don't validate, mark step skipped, navigate
- Save & exit: save draft, redirect to dashboard
- Exit without saving: confirm + clear draft

Disabled states:
- Next disabled when current step invalid (with reason in tooltip)
- Back disabled on first step
- Skip only shown when applicable

Keyboard:
- Enter: submit current step (Next)
- Cmd/Ctrl + Enter: skip step (if applicable)
- Escape: exit confirmation

Mobile:
- Sticky bottom navigation (Back / Next)
- Full-width buttons
- Spacious padding for thumb taps

Output:
1. Button component (with variants for state)
2. Sticky-bottom mobile pattern
3. Keyboard handlers
4. Confirmation modal for "exit without saving"
5. Disabled-with-reason tooltip

The "Save & exit" pattern: critical for long flows (KYC, application). Without it, users who can't finish in one sitting just abandon.

8. Conditional rendering — show only relevant fields

Most multi-step forms have fields that only appear based on prior answers. Handle gracefully.

Conditional fields in wizards.

Trigger types:
- Radio button selection ("Are you a business?" → show business fields)
- Boolean toggle
- Multi-select (show fields for each selected option)
- Async result (verify address → show suggested correction)

Implementation:
- Watch parent field via React Hook Form's watch() or useFormContext
- Conditionally render child fields based on watched value
- Animation: subtle slide-down on appear (not jarring jump)
- Validation: skip validation for hidden fields (don't block "Next" because of invisible field)

Edge cases:
- User answers Yes (shows fields) → No (hides) → Yes again → preserve or reset values?
- Recommended: preserve unless explicitly resetting
- User skipping fields by toggling answers is a hint to simplify

Branching steps (vs branching fields):
- Branching fields = within same step
- Branching steps = different next step entirely
- Prefer branching fields when possible (one less navigation)

Output:
1. Conditional field hook
2. Animation on show/hide
3. Validation skip for hidden fields
4. Value preservation policy
5. Anti-pattern detection (too many branches = simplify)

The animation rule: avoid jarring jumps. Use Framer Motion's AnimatePresence or shadcn's collapse to ease transitions. Sub-200ms feels organic.

9. Submission + success state

The end of a wizard is its own UX problem.

Wizard submission and success state.

Submission flow:
1. User clicks "Submit" on final step
2. Show loading state (button spinner + disable form)
3. Send all data to server (POST /api/onboarding)
4. Server validates everything one more time
5. On success: navigate to success state
6. On failure: stay on current step with error

Success state options:
- Dedicated success page (full-screen confirmation)
- Modal overlay (less commitment)
- Redirect to dashboard with toast

Success page should:
- Confirm completion clearly ("Welcome aboard, you're all set")
- Show next action ("Take the product tour" / "Invite teammates")
- Provide reference (confirmation #, email sent)
- Avoid dead-end (user shouldn't have to find their way back)

Failure handling:
- Network error: retry button + don't lose data
- Validation error: jump to step with error + highlight field
- Server error (500): apologize + offer support
- Already-submitted: show success state (idempotency)

Email confirmation:
- Send transactional email after submission
- Include reference / next steps
- See email-deliverability-chat for delivery

Output:
1. Submission handler with retry
2. Success page template
3. Error-recovery UX (which-step-to-jump-to logic)
4. Idempotency strategy (don't double-submit)
5. Confirmation email template

The dead-end problem: success pages that say "Done!" with no next action. Users sit there confused. Always include a clear next CTA.

10. Accessibility + keyboard

Multi-step forms are notoriously hard for screen reader users. Get accessibility right.

Accessibility for wizards.

Required:
- Each step has a unique <h1> announcing step
- Progress indicator has aria-label ("Step 2 of 5: Company info")
- Active step on stepper has aria-current="step"
- Form errors announced via aria-live="polite"
- Field errors connected via aria-describedby
- Submit button announces success/failure on click
- Focus management on step navigation (move to first input or step heading)

Keyboard:
- Tab through fields in logical order
- Enter to submit step (Next)
- Escape to cancel / exit
- Cmd/Ctrl + Enter to skip (if applicable)

Screen reader testing:
- VoiceOver (Mac) + NVDA (Windows)
- Verify step announcements
- Verify error announcements
- Verify focus moves to right element on transition

Common failures:
- Focus stays on Next button after step changes (user lost in space)
- Errors not announced
- Progress indicator visually-only (no aria)
- Conditional fields not announced when shown

Output:
1. ARIA attributes for stepper, progress, fields
2. Focus-management on step transitions
3. Error-announcement pattern
4. Test plan with screen readers
5. Reduced-motion handling for animations

The focus-management failure: after clicking "Next," the page changes but focus stays on the disabled "Next" button. Screen-reader users have no idea where they are. Fix: focus first input or step heading on navigation.

What Done Looks Like

A v1 multi-step form for B2B SaaS in 2026:

  • 3-7 steps (not 12; not 1)
  • Each step has its own URL (browser back works)
  • Server-side draft persistence (save & exit works)
  • Per-step validation with field-level errors
  • Progress indication (stepper or bar)
  • Navigation: Next / Back / Skip / Save & exit / Exit
  • Conditional fields with smooth show/hide
  • Submission with retry on failure
  • Success page with clear next action
  • Accessibility: focus management + ARIA + keyboard
  • Mobile: sticky bottom nav + thumb-friendly inputs

Add later when product is mature:

  • Branching paths (XState model)
  • Multi-language support
  • Step analytics (which steps drop users)
  • A/B test field ordering
  • Smart defaults from prior data

The mistake to avoid: too many steps. Each click is friction. Combine when fields are short. Most "wizards" should be 3 steps or fewer.

The second mistake: no draft persistence on long forms. KYC / application flows that take 20+ minutes lose users when they refresh or get pulled away.

The third mistake: validation only at submit. Users fill all 7 steps, click submit, and learn step 2 was wrong. Validate per-step.

See Also