Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Forms

Form Wizard

Forms

Form Wizard React component for Tailwind CSS v4

FieldInline Edit
Live props
Layout
stepper
fullScreenOnMobile
Behavior
linear
animate
keepMounted
Appearance
size
tone
color
radius
Content
showStepHeading
State
defaultValue
Code
import { FormWizard } from "@/components/ui/form-wizard"

<FormWizard />
  1. Home
  2. Components
  3. Forms
  4. Form Wizard
Pro

Form Wizard React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React multi-step form wizard with a stepper, sync or async validation per step, a summary step and a full-screen phone layout.

Fill in a step and press Continue (or Enter in any single-line field, Cmd+Enter / Ctrl+Enter in a textarea): the step's validators run, and on success the step is marked with a check and the next one slides in from the side you are heading, while the card's height follows on a spring. A failed step turns its marker into a red "!", shakes the Continue button and puts focus on the first invalid field; an async check shows a spinner on the button after a moment. Back never validates. Click a completed step in the stepper to jump back to it (with linear={false} any step), and on the summary step the Edit links do the same. Optional steps show Skip. On the last step the button submits and shows a spinner while onComplete runs, then a check. Tab order is the stepper, the fields, then Back, Skip and Continue; after a step change focus lands on the new step's heading and screen readers hear "Step 2 of 5: Details". Below 768px the stepper becomes a compact progress bar with the step title, and with fullScreenOnMobile the wizard fills the screen with a sticky header, a scrolling body and a sticky footer of 48px buttons above the safe area (in the flow, so it rides above the keyboard). Steps change only through the buttons: no swipe, so sliders and carousels inside a step keep their gestures. Under reduced motion steps crossfade and nothing shakes.

Form Wizard is a Pro React component in the React form components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add form-wizard, together with Button, Motion presets, Tones, useMediaQuery, useControllableState and 1 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add form-wizard

Form Wizard is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import FormWizard from @/components/ui/form-wizard.

import { Controller, FormProvider, useForm, useFormContext } from "react-hook-form"
import { FormWizard, FormWizardSummary, useFormWizardValidation, type WizardStep } from "@/components/ui/form-wizard"
import { Input } from "@/components/ui/input"
import { CurrencyInput } from "@/components/ui/currency-input"

type Listing = { title: string; price: number | null }

function Details() {
  const { register, control, trigger, formState } = useFormContext<Listing>()
  useFormWizardValidation(() => trigger(["title", "price"]))
  return (
    <div className="flex flex-col gap-4">
      <Input
        label="Title"
        error={formState.errors.title?.message}
        {...register("title", { minLength: { value: 10, message: "The title needs at least 10 characters" } })}
      />
      <Controller
        name="price"
        control={control}
        rules={{ required: "Add a price" }}
        render={({ field, fieldState }) => (
          <CurrencyInput label="Price" currency="USD" locale="en-US" value={field.value} onValueChange={field.onChange} onBlur={field.onBlur} ref={field.ref} error={fieldState.error?.message} />
        )}
      />
    </div>
  )
}

function Review() {
  const { getValues } = useFormContext<Listing>()
  return <FormWizardSummary editLabel="Edit" sections={[{ step: "details", items: [{ label: "Title", value: getValues("title") }] }]} />
}

const steps: WizardStep[] = [
  { id: "details", title: "Details", content: <Details /> },
  { id: "review", title: "Review", content: <Review /> },
]

export function PostListing() {
  const form = useForm<Listing>({ defaultValues: { title: "", price: null } })
  return (
    <FormProvider {...form}>
      <FormWizard
        steps={steps}
        labels={{ submit: "Publish", step: (i, n) => `Step ${i} of ${n}` }}
        onComplete={() => fetch("/api/listings", { method: "POST", body: JSON.stringify(form.getValues()) })}
      />
    </FormProvider>
  )
}

Props

  • steps
    WizardStep[]

    { id, title, description?, icon?, content, optional?, validate? }. validate returns a boolean or a promise; a throw or rejection counts as failed and is logged. Alternatively pass <FormWizardStep> children with the same props (content is their children). An empty list renders nothing.

  • stepperdefault "horizontal"
    "horizontal" | "vertical" | "progress" | "dots" | "none"

    The header from 768px up: numbered markers with connectors, a side column with descriptions, a progress bar with the step title, dots for onboardings, or nothing. Below 768px every stepper except none becomes the compact progress header.

  • lineardefault true
    boolean

    Only Continue moves forward; completed steps stay clickable. false makes every step clickable (going forward still validates the current step first).

  • sizedefault "md"
    "sm" | "md"

    Markers 24 / 28px, step titles 13 / 14px, footer buttons sm / md.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The active marker, the connector and progress fills, completed markers' tint and the Continue button.

  • colordefault ""
    string

    Any CSS color; replaces the tone everywhere. Text on the fill turns black or white by lightness.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corner shape of the footer buttons, scaled per size like the button's.

  • showStepHeadingdefault true
    boolean

    The step title and description above the fields. The title hides on its own wherever the header already shows it (phones, the progress stepper); false hides both but keeps the title for screen readers and as the focus target.

  • fullScreenOnMobiledefault true
    boolean

    Below 768px the wizard fills its container's height: a sticky blurred header, a body that scrolls on its own and a sticky footer with 48px buttons (Continue full width, Back as an icon) padded for the safe area. Give the container a height (h-dvh for a page).

  • animatedefault true
    boolean

    Steps slide 32px in the direction you move, the body's height follows on a spring, fills grow and a failed Continue shakes. false swaps steps instantly. Reduced motion always crossfades.

  • keepMounteddefault false
    boolean

    Keeps every step mounted (inactive ones hidden and inert), for forms that keep their state in the DOM. react-hook-form does not need it: it keeps the values of unmounted fields.

  • value
    string

    The current step id, controlled. Pair it with onValueChange. An unknown id shows the first step; a removed active step moves to the nearest one.

  • defaultValuedefault ""
    string

    The starting step id; empty (or an unknown id) starts on the first step. In a linear wizard the steps before it count as completed.

  • children
    ReactNode

    <FormWizardStep> elements instead of the steps array; other children are ignored.

  • onValueChange
    (value: string) => void

    Called with the new step id on every step change.

  • onComplete
    (event: FormEvent<HTMLFormElement>) => (void | Promise<unknown>)

    The last step's submit, after its validation. A promise shows the button loading, then a check (labels.done); a rejection shakes it. After an await read the form from event.target. Without onComplete a form action (formProps.action) runs instead.

  • footer
    ReactNode

    Replaces the default footer; build one with useFormWizard() (a type="submit" button acts as Continue). null hides the footer.

  • labels
    Partial<{ back; next; submit; skip; done; step: (index, count) => string; optional; completed; error; progress }>

    Every visible and announced string, merged over the English defaults: back "Back", next "Continue", submit "Submit", skip "Skip", done "Done", step (i, n) => "Step i of n", optional "Optional", completed "Completed", error "Needs attention" (both read after a step's title), progress "Progress" (names the stepper).

  • formProps
    ComponentProps<"form">

    Extra props for the root <form> (action, id, method). Other unknown props land on the form too.

  • classNames
    Partial<Record<"header" | "stepper" | "stepItem" | "stepMarker" | "stepTitle" | "stepDescription" | "connector" | "progress" | "progressBar" | "progressText" | "main" | "body" | "panel" | "panelTitle" | "panelDescription" | "footer" | "back" | "skip" | "next", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the root <form>.

Parts

  • FormWizardStep

    component

    Declares a step as a child of FormWizard instead of the steps array; its children are the content. Props: Omit<WizardStep, "content"> & { children?: ReactNode }.

  • useFormWizard

    hook

    The wizard's state and actions inside FormWizard, for custom footers and summaries. next() and goTo() resolve to false when validation blocks them. Signature: () => { steps, value, index, count, isFirst, isLast, completed, errors, skipped, validating, submitting, done, next(), back(), goTo(id), skip() }.

  • useFormWizardValidation

    hook

    Registers a validator for the step the calling component renders in; removed on unmount. Validators run in order after the step's own validate. With react-hook-form: useFormWizardValidation(() => form.trigger(["title", "price"])). Signature: (validate: () => boolean | Promise<boolean>) => void.

  • FormWizardSummary

    component

    A review step: one list group per section (title defaults to the step's) with an Edit link that goes back to that step. Props: { sections: { step: string; title?: ReactNode; items: { label: ReactNode; value: ReactNode }[] }[]; editLabel?: string; editable?: boolean }.

  • WizardStep

    type

    One step of steps: { id, title, description?, icon?, content, optional?, validate? }.

  • FormWizardState

    type

    What useFormWizard returns: the steps, current index, completed / error / skipped sets, flags, and next, back, goTo and skip.

Examples

Vertical stepper

A side column with descriptions once the wizard is 672px wide on a 768px+ screen; narrower, the compact progress header takes over.

Progress header on a compact card

stepper="progress" and size="sm": a bar, "Step 2 of 3" and the step title.

Jump anywhere

linear={false}: every step is clickable. A custom color and pill buttons.

Async validation

A validator registered by the step checks the EIN for a second; Next shows a spinner meanwhile.

Dots for an onboarding

stepper="dots" with brand orange and an optional step you can skip.

Custom footer

useFormWizard() builds a footer with a Save draft button next to Next.

Dependencies

motionlucide-reactbuttonmotiontonesuse-media-queryuse-controllable-stateui-config

Files

  • components/ui/form-wizard.tsx

FAQ

Is the React Form Wizard component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Form Wizard?

Run npx wingo-ui@latest add form-wizard. It also adds the Wingo UI items it builds on (Button, Motion presets, Tones, useMediaQuery, useControllableState and 1 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/form-wizard.

How does Form Wizard behave on phones?

Below 768px the stepper becomes a compact progress bar with the step title, and with fullScreenOnMobile the wizard fills the screen with a sticky header, a scrolling body and a sticky footer of 48px buttons above the safe area (in the flow, so it rides above the keyboard).

See also

StepsCheckout stepsFieldInputRadio GroupFile Upload

Used in

Checkout stepsCSV ImportPost Listing Wizard

More React form components

Inline EditInvoice line itemsRich Text EditorSchema BuilderAddress InputBooking Widget
All 9 React form components

Articles using this component

  • React Signature Pad: Smooth Ink, Typed Mode, PNG and SVG

    Component guides · Oct 16, 2026 · 9 min read

  • React Multi Step Form with Zod, Saved Drafts and Motion

    Component guides · Oct 13, 2026 · 12 min read

PreviousFieldNext Inline Edit

Post a listing

  1. 1Category
  2. 2Details
  3. 3Photos
  4. 4Contact
  5. 5Review
Step 1 of 5Category

Category

Choose where your listing appears.

What are you selling?