Open source ยท TypeScript ยท Zod + react-hook-form

Multi-step forms for React, done right

Headless hooks and optional UI components for building type-safe wizard forms with per-step Zod validation. Zero CSS imposed.

Get startedAPI reference
pnpm add react-formsteps-core

Everything you need, nothing you don't

๐Ÿงฉ

Headless by default

Zero UI opinions. Bring your own design system, CSS framework, or component library.

๐Ÿ”’

Per-step validation

Each step validates only its own Zod schema before advancing. No full-form re-validation.

โšก

Built on react-hook-form

Fully compatible with the react-hook-form ecosystem. Use any resolver or field array.

๐ŸŽฏ

Type-safe to the core

Strict TypeScript throughout. Inferred types from Zod schemas flow into your form fields.

๐Ÿชถ

Tiny footprint

Tree-shakeable. No CSS bundled. Only ship what you actually use.

๐Ÿ—๏ธ

Flexible architecture

Use the headless hooks alone, or drop in the optional UI components for a quick start.

How it works

Every click on "Next" runs through this sequence automatically.

โœ๏ธ User fills step
โ†’
Click Next
โ†’
๐Ÿ” validateStep(schema, data)
โ†’
Valid โœ“
โ†’
โญ๏ธ Advance to next step
Invalid โœ—
โ†’
๐Ÿšซ Show field errors, stay on step
on last step
๐Ÿš€ Click Submit
โ†’
๐Ÿ” validateAllSteps(schemas, data)
โ†’
๐Ÿ”— Merge all step data
โ†’
โœ… onSubmit(data)
Live preview

See it in action

Each step only validates its own schema. You can't advance until the current step is valid โ€” and going back never loses accumulated data.

  • Progress tracked automatically
  • Per-step Zod validation
  • Back navigation preserves data
  • Final submit merges all steps
Personal infoStep 1 / 3

Simple API, powerful results

Drop in the hooks and keep full control over your UI.

MyForm.tsx
import { useSteps, useStepForm } from 'react-formsteps-core';
import { z } from 'zod';

const schema = z.object({ email: z.string().email() });

function MyForm() {
  const { currentStep, next, prev, isLast, progress } = useSteps({
    totalSteps: 3,
  });

  const { form, nextWithValidation } = useStepForm({ schema });

  return (
    <form>
      <progress value={progress} max={100} />
      <input {...form.register('email')} />
      <button onClick={prev}>Back</button>
      <button onClick={() => nextWithValidation().then(ok => ok && next())}>
        {isLast ? 'Submit' : 'Next'}
      </button>
    </form>
  );
}

Ready to build?

Check the docs and have your first multi-step form running in minutes.

Read the docs โ†’