Skip to content
WANCRAFT

F.23Forms & Inputs

Multi-Step Form

A typed, multi-stage form with per-step validation, direction-aware transitions and a success state.

ReactMotionTailwindadvanced

Overview

Multi-Step Form turns a typed `steps` config into a guided form. Each step validates before advancing (required, email, URL, number and custom rules), errors are linked to their fields, and focus moves to the first invalid field or to the new step's heading. Steps slide or fade in the direction of travel while the frame animates between heights. The progress indicator marks the current step with aria-current and announces "Step 2 of 3". The final step awaits an async onSubmit and resolves into a success state.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/multi-step-form.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/multi-step-form.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/multi-step-form.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
1 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { MultiStepForm, type MultiStepFormStep } from "@/components/ui/multi-step-form";

export function MultiStepFormExample() {
  const steps: MultiStepFormStep[] = [
    {
      id: "contact",
      title: "About you",
      fields: [
        { name: "name", label: "Full name", required: true, autoComplete: "name" },
        { name: "email", label: "Email", type: "email", required: true, autoComplete: "email" },
      ],
    },
    {
      id: "project",
      title: "The project",
      fields: [
        {
          name: "type",
          label: "Project type",
          type: "radio",
          required: true,
          options: [
            { label: "Website", value: "website" },
            { label: "Product", value: "product" },
          ],
        },
      ],
    },
    {
      id: "details",
      title: "Details",
      fields: [{ name: "message", label: "Tell us more", type: "textarea", required: true }],
    },
  ];

  return (
    <MultiStepForm
      steps={steps}
      onSubmit={async (values) => console.log(values)}
      className="max-w-lg"
    />
  );
}

03Props

PropTypeDefaultDescription
stepsMultiStepFormStep[]—Required. { id, title, description?, fields: MultiStepField[] } per step.
onSubmit(values: Record<string, string>) => void | Promise<void>—Called after the last step validates. Awaited; a rejection shows a retryable error.
initialValuesRecord<string, string>—Initial field values (also used by "Start over").
onStepChange(index: number, step: MultiStepFormStep) => void—Fires when the visible step changes.
transition"slide" | "fade""slide"Step transition; slide follows the direction of travel.
progress"steps" | "bar""steps"Segmented step list or a single bar.
durationnumber320Transition duration in ms.
accentstring"#FF7448"Accent for progress, focus and selection.
nextLabel / backLabel / submitLabelstring"Continue" / "Back" / "Send enquiry"Button labels.
successTitle / successMessagestring—Copy for the success state.
MultiStepField{ name; label; type?; required?; placeholder?; hint?; autoComplete?; options?; validate? }—type: text | email | tel | url | number | textarea | select | radio. validate(value, values) returns an error string or nothing.
...propsFormHTMLAttributes—Forwarded to the <form> (except onSubmit/children).

04Features

  • 01Typed steps config with built-in required, email, URL and number checks plus custom validators
  • 02Direction-aware slide or fade, with the frame animating between step heights
  • 03Completed steps can be revisited from the progress indicator
  • 04Async submit with a pending state, retryable error and success screen
  • 05Enter advances from any single-line field
motion · tweenmotion · layoutinput · clickinput · keyboardinput · focus

05Accessibility

  • Progress list marks the current step with aria-current="step" and announces "Step 2 of 3: Title" in a polite live region.
  • Each step is a <fieldset> labelled by its heading; focus moves to that heading on navigation.
  • Invalid fields get aria-invalid and aria-describedby pointing at their message; focus jumps to the first invalid field.
  • Radio options are native radios inside role="radiogroup" — arrow keys work, focus is visible on the card.
  • prefers-reduced-motion removes slide, fade and height animation.