Multi-step forms (wizards) guide users through a process in stages, reducing cognitive load. Each step collects specific information before moving to the next.
Complete implementation:
1import { useState } from "react";23interface FormData {4 personal: { name: string; email: string };5 address: { street: string; city: string; zip: string };6 payment: { card: string; expiry: string; cvv: string };7}89const STEPS = ["Personal", "Address", "Payment"] as const;1011function MultiStepForm() {12 const [step, setStep] = useState(0);13 const [formData, setFormData] = useState<FormData>({14 personal: { name: "", email: "" },15 address: { street: "", city: "", zip: "" },16 payment: { card: "", expiry: "", cvv: "" },17 });1819 const updateStep = (stepData: Partial<FormData[keyof FormData]>) => {20 setFormData(prev => ({21 ...prev,22 [STEPS[step].toLowerCase()]: {23 ...prev[STEPS[step].toLowerCase() as keyof FormData],24 ...stepData,25 },26 }));27 };2829 const handleSubmit = async () => {30 console.log("Submitting:", formData);31 await submitForm(formData);32 };3334 return (35 <div style={{ maxWidth: "400px", margin: "0 auto" }}>36 {/* Progress indicator */}37 <div style={{ display: "flex", justifyContent: "space-between", marginBottom: "24px" }}>38 {STEPS.map((label, index) => (39 <div40 key={label}41 style={{42 padding: "8px 16px",43 borderRadius: "4px",44 backgroundColor: index <= step ? "#4CAF50" : "#ddd",45 color: index <= step ? "white" : "#666",46 }}47 >48 {label}49 </div>50 ))}51 </div>5253 {/* Step content */}54 {step === 0 && (55 <PersonalStep data={formData.personal} onChange={updateStep} />56 )}57 {step === 1 && (58 <AddressStep data={formData.address} onChange={updateStep} />59 )}60 {step === 2 && (61 <PaymentStep data={formData.payment} onChange={updateStep} />62 )}6364 {/* Navigation */}65 <div style={{ display: "flex", justifyContent: "space-between", marginTop: "24px" }}>66 {step > 0 && (67 <button onClick={() => setStep(s => s - 1)}>Back</button>68 )}69 {step < STEPS.length - 1 ? (70 <button onClick={() => setStep(s => s + 1)}>Next</button>71 ) : (72 <button onClick={handleSubmit}>Submit</button>73 )}74 </div>75 </div>76 );77}7879// Step components80function PersonalStep({ data, onChange }) {81 return (82 <div>83 <input84 value={data.name}85 onChange={e => onChange({ name: e.target.value })}86 placeholder="Name"87 />88 <input89 value={data.email}90 onChange={e => onChange({ email: e.target.value })}91 placeholder="Email"92 />93 </div>94 );95}
With React Hook Form (recommended for production):
1import { useForm } from "react-hook-form";23function StepForm() {4 const [step, setStep] = useState(0);5 const { register, handleSubmit, trigger, formState: { errors } } = useForm();67 const nextStep = async () => {8 const isValid = await trigger(); // validate current step fields9 if (isValid) setStep(s => s + 1);10 };1112 return (13 <form onSubmit={handleSubmit(onSubmit)}>14 {step === 0 && (15 <div>16 <input {...register("name", { required: true })} placeholder="Name" />17 {errors.name && <span>Name is required</span>}18 </div>19 )}20 {step === 1 && (21 <div>22 <input {...register("address", { required: true })} placeholder="Address" />23 </div>24 )}25 <button type="button" onClick={nextStep}>Next</button>26 {step > 0 && <button type="button" onClick={() => setStep(s => s - 1)}>Back</button>}27 {step === 1 && <button type="submit">Submit</button>}28 </form>29 );30}
Best practices: