React Form Wizard
Getting Started

Your First Wizard

Build a working three-step form, then add validation and collect the data.

Your First Wizard

We will build a three-step signup form, then add validation and read the data back out. Each step below is complete and runnable.

1. The shell

Wrap FormWizard.TabContent elements in a FormWizard. Each one becomes a step.

import FormWizard from "react-form-wizard-component";
import "react-form-wizard-component/styles.css";

export default function Signup() {
  return (
    <FormWizard>
      <FormWizard.TabContent title="Account">
        <input placeholder="Email" />
      </FormWizard.TabContent>
      <FormWizard.TabContent title="Profile">
        <input placeholder="Full name" />
      </FormWizard.TabContent>
      <FormWizard.TabContent title="Review">
        <p>Looks good?</p>
      </FormWizard.TabContent>
    </FormWizard>
  );
}

You already have a progress bar, Back/Next/Finish buttons, keyboard navigation, swipe support on touch devices, and screen-reader announcements.

Form wizard with 3 steps. Currently on step 1.

Sample 1: Basic Wizard

Simple children API

Step 1 of 3: Step 1

Welcome

This is a basic wizard using the children API.

2. Know when it finishes

onComplete fires when Finish is pressed on a valid last step.

<FormWizard onComplete={() => console.log("done")}>

3. Hold the data in the wizard

Rather than wiring up your own state, let the wizard carry it. Switch to the schema API, which also unlocks conditional steps and per-step validation.

import { useRef } from "react";
import FormWizard, {
  type FormWizardMethods,
  type FormWizardSchema,
} from "react-form-wizard-component";
import "react-form-wizard-component/styles.css";

export default function Signup() {
  const wizard = useRef<FormWizardMethods>(null);
  const set = (patch: Record<string, unknown>) =>
    wizard.current?.updateData(patch);

  const schema: FormWizardSchema = {
    initialData: { email: "", name: "" },
    steps: [
      {
        id: "account",
        title: "Account",
        // `content` can be a function of the current data.
        content: ({ data }) => (
          <input
            value={String(data.email ?? "")}
            onChange={(e) => set({ email: e.target.value })}
            placeholder="Email"
          />
        ),
      },
      {
        id: "profile",
        title: "Profile",
        content: ({ data }) => (
          <input
            value={String(data.name ?? "")}
            onChange={(e) => set({ name: e.target.value })}
            placeholder="Full name"
          />
        ),
      },
      {
        id: "review",
        title: "Review",
        content: ({ data }) => (
          <p>
            {String(data.name)} — {String(data.email)}
          </p>
        ),
      },
    ],
  };

  return (
    <FormWizard
      ref={wizard}
      schema={schema}
      onComplete={(data) => console.log("submitting", data)}
    />
  );
}

4. Stop people skipping ahead

A step's validate returns true to allow navigation, or a string to block it and supply the message.

{
  id: "account",
  title: "Account",
  content: ({ data }) => /* … */,
  validate: ({ data }) =>
    String(data.email ?? "").includes("@") ? true : "Enter a valid email",
}

Next is now blocked and the step marker turns red until the email is valid.

validate runs on every render. Keep it synchronous and free of side effects — never await, never call setState from it. For asynchronous checks, see the pattern in the validation guide.

Real schemas are easier with an adapter:

import { zodValidator } from "react-form-wizard-component";
import { z } from "zod";

const account = z.object({ email: z.string().email("Enter a valid email") });

validate: zodValidator(account, { pick: ["email"] });

5. Hide steps that do not apply

condition removes a step entirely when it returns false — the progress bar and step count adjust automatically.

{
  id: "billing",
  title: "Billing",
  condition: ({ data }) => data.plan === "premium",
  content: <BillingFields />,
}

Where to go next

If you want to…Read
Validate with Zod or react-hook-formValidation
Use your own markup entirelyHeadless
Restyle or unstyle itTheming
Survive a page reloadPersistence
See a complete buildReal-world examples
Look up a propAPI reference

On this page