WebberUI

Multi-step Form

A multi-step form container with direction-aware sliding transitions and a switchable progress indicator style.

This is a WebberUI Pro component

Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.

How to install Pro components →See the plans →

Loading preview…
npx shadcn@latest add "https://webberui.com/r/multi-step-form.json?t=<install token>"

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

<MultiStepForm />

Installation

npx shadcn@latest add "https://webberui.com/r/multi-step-form.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/multi-step-form.

Usage

Declare each step with <MultiStepFormStep>; the container makes a direction-aware sliding transition based on the current step and renders the progress indicator and the back / next control bar automatically.

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

<MultiStepForm variant="steps" onComplete={() => console.log("done")}>
  <MultiStepFormStep label="Account">{/* ...fields */}</MultiStepFormStep>
  <MultiStepFormStep label="Profile">{/* ...fields */}</MultiStepFormStep>
  <MultiStepFormStep label="Confirm">{/* ...summary */}</MultiStepFormStep>
</MultiStepForm>;

Custom control bar

Set controls to false and use the useMultiStepForm() hook to assemble your own navigation anywhere (so you can validate before calling next()):

import { useMultiStepForm } from "@/components/ui/multi-step-form";

function Footer() {
  const { back, next, isFirst, isLast } = useMultiStepForm();
  return (
    <div>
      <button onClick={back} disabled={isFirst}>
        Back
      </button>
      <button onClick={next}>{isLast ? "Submit" : "Next"}</button>
    </div>
  );
}

Controlled mode

Pass step and onStepChange to take full control of the current step — ideal for gating steps behind form validation.

const [step, setStep] = React.useState(0);

<MultiStepForm step={step} onStepChange={(next) => setStep(next)}>
  {/* ...steps */}
</MultiStepForm>;

Props

MultiStepForm

PropTypeDefaultDescription
childrenReactNodeThe step declarations, all <MultiStepFormStep>
stepnumberControlled current step index; when omitted, it is uncontrolled
defaultStepnumber0Initial step index in uncontrolled mode
onStepChange(step: number, direction: 1 | -1) => voidFires when the step changes, with the new index and the direction
onComplete() => voidFires when "Done" is pressed on the last step
variant"bar" | "dots" | "steps" | false"steps"Progress indicator style; false hides it
controlsbooleantrueWhether to render the built-in control bar
backLabelstring"上一步"Text of the back button
nextLabelstring"下一步"Text of the next button
completeLabelstring"完成"Text of the final-step button

MultiStepFormStep

PropTypeDefaultDescription
labelstringStep title shown in the progress indicator
childrenReactNodeStep content
classNamestringAppended to the step container's className

useMultiStepForm()

Returns { step, total, direction, isFirst, isLast, goTo, next, back, reducedMotion } for use in a custom control bar or progress indicator.

How it works

  • Three progress styles: bar progress bar, dots (the current dot stretches into a pill), and steps numbered steps (completed ones get a checkmark and the connecting lines colour in with the progress).
  • Direction awareness: going forward, the new step slides in from the right and the old one exits to the left; going back is the reverse. The direction is derived during render from the previous index, and is correct in both controlled and uncontrolled modes.
  • The viewport uses overflow-hidden to clip the horizontal slide, and popLayout takes the exiting step out of flow so the entering step decides the container height, preventing the height from collapsing during the transition.

Accessibility

  • Steps in the progress indicator that are current or already visited can be clicked to jump back (goTo), while steps not yet reached are disabled, preserving the fill-in order.
  • The current step is marked aria-current="step"; the progress bar provides role="progressbar" and aria-valuenow.
  • Step changes are announced as "step x of y" through an aria-live="polite" region.
  • The built-in control bar uses native buttons that are keyboard-operable with a focus-visible ring; the back button is disabled on the first step.
  • When the user has "reduce motion" enabled at the system level, the slide degrades to a brief fade in / fade out, and the progress fill jumps straight into position.

On this page