- Gallery
- Forms & Input
- Multi-Step Form
New
Multi-Step Form
A 3-step form wizard with progress indicators, validation, and step navigation.
Forms & Inputformwizardstepsprogressvalidation
Dependencies
Other dependencies:
@/components/ui/card@/components/ui/button@/components/ui/input@/components/ui/label@/components/ui/progress
How to use this component
Copy the code below into your project. Make sure you have the required shadcn/ui dependencies installed. Then import and use the component in your pages or layouts.
Code
1"use client";23import { useState } from 'react';4import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';5import { Button } from '@/components/ui/button';6import { Input } from '@/components/ui/input';7import { Label } from '@/components/ui/label';8import { Progress } from '@/components/ui/progress';9import { Check, ChevronRight, ChevronLeft } from 'lucide-react';1011export default function MultiStepForm() {12 const [currentStep, setCurrentStep] = useState(1);13 const [formData, setFormData] = useState({14 name: '',15 email: '',16 street: '',17 city: '',18 zip: ''19 });20 const [errors, setErrors] = useState<Record<string, string>>({});2122 const updateField = (field: string, value: string) => {23 setFormData(prev => ({ ...prev, [field]: value }));24 if (errors[field]) {25 setErrors(prev => ({ ...prev, [field]: '' }));26 }27 };2829 const validateStep = (step: number) => {30 const newErrors: Record<string, string> = {};31 32 if (step === 1) {33 if (!formData.name.trim()) newErrors.name = 'Name is required';34 if (!formData.email.trim()) newErrors.email = 'Email is required';35 } else if (step === 2) {36 if (!formData.street.trim()) newErrors.street = 'Street is required';37 if (!formData.city.trim()) newErrors.city = 'City is required';38 if (!formData.zip.trim()) newErrors.zip = 'ZIP is required';39 }40 41 setErrors(newErrors);42 return Object.keys(newErrors).length === 0;43 };4445 const nextStep = () => {46 if (validateStep(currentStep) && currentStep < 3) {47 setCurrentStep(prev => prev + 1);48 }49 };5051 const prevStep = () => {52 if (currentStep > 1) {53 setCurrentStep(prev => prev - 1);54 }55 };5657 const steps = [58 { id: 1, title: 'Personal' },59 { id: 2, title: 'Address' },60 { id: 3, title: 'Review' }61 ];6263 return (64 <Card className="w-full max-w-lg mx-auto">65 <CardHeader>66 <CardTitle>Create Account</CardTitle>67 </CardHeader>68 <CardContent className="space-y-6">69 <div className="space-y-2">70 <div className="flex items-center justify-between">71 {steps.map((step, idx) => (72 <div key={step.id} className="flex items-center flex-1">73 <div className={`flex items-center justify-center w-8 h-8 rounded-full border-2 \` +74 `${currentStep > step.id ? 'border-green-500 bg-green-500 text-white' : ''}\` +75 `${currentStep === step.id ? 'border-primary bg-primary text-primary-foreground' : ''}\` +76 `${currentStep < step.id ? 'border-muted text-muted-foreground' : ''}`}>77 {currentStep > step.id ? <Check className="w-4 h-4" /> : step.id}78 </div>79 {idx < steps.length - 1 && (80 <div className={`flex-1 h-0.5 mx-2 \` +81 `${currentStep > step.id ? 'bg-green-500' : 'bg-muted'}`} />82 )}83 </div>84 ))}85 </div>86 <Progress value={(currentStep / 3) * 100} className="h-2" />87 </div>8889 {currentStep === 1 && (90 <div className="space-y-4">91 <div className="space-y-2">92 <Label htmlFor="name">Full Name</Label>93 <Input94 id="name"95 value={formData.name}96 onChange={e => updateField('name', e.target.value)}97 className={errors.name ? 'border-red-500' : ''}98 placeholder="John Doe"99 />100 {errors.name && <p className="text-sm text-red-500">{errors.name}</p>}101 </div>102 <div className="space-y-2">103 <Label htmlFor="email">Email</Label>104 <Input105 id="email"106 type="email"107 value={formData.email}108 onChange={e => updateField('email', e.target.value)}109 className={errors.email ? 'border-red-500' : ''}110 placeholder="john@example.com"111 />112 {errors.email && <p className="text-sm text-red-500">{errors.email}</p>}113 </div>114 </div>115 )}116117 {currentStep === 2 && (118 <div className="space-y-4">119 <div className="space-y-2">120 <Label htmlFor="street">Street Address</Label>121 <Input122 id="street"123 value={formData.street}124 onChange={e => updateField('street', e.target.value)}125 className={errors.street ? 'border-red-500' : ''}126 placeholder="123 Main St"127 />128 {errors.street && <p className="text-sm text-red-500">{errors.street}</p>}129 </div>130 <div className="grid grid-cols-2 gap-4">131 <div className="space-y-2">132 <Label htmlFor="city">City</Label>133 <Input134 id="city"135 value={formData.city}136 onChange={e => updateField('city', e.target.value)}137 className={errors.city ? 'border-red-500' : ''}138 placeholder="New York"139 />140 {errors.city && <p className="text-sm text-red-500">{errors.city}</p>}141 </div>142 <div className="space-y-2">143 <Label htmlFor="zip">ZIP Code</Label>144 <Input145 id="zip"146 value={formData.zip}147 onChange={e => updateField('zip', e.target.value)}148 className={errors.zip ? 'border-red-500' : ''}149 placeholder="10001"150 />151 {errors.zip && <p className="text-sm text-red-500">{errors.zip}</p>}152 </div>153 </div>154 </div>155 )}156157 {currentStep === 3 && (158 <div className="space-y-4">159 <h3 className="font-semibold">Review Your Information</h3>160 <div className="bg-muted p-4 rounded-lg space-y-2">161 <div><span className="text-muted-foreground">Name:</span> {formData.name}</div>162 <div><span className="text-muted-foreground">Email:</span> {formData.email}</div>163 <div><span className="text-muted-foreground">Address:</span> {formData.street}</div>164 <div><span className="text-muted-foreground">City:</span> {formData.city}</div>165 <div><span className="text-muted-foreground">ZIP:</span> {formData.zip}</div>166 </div>167 </div>168 )}169170 <div className="flex justify-between pt-4">171 <Button172 variant="outline"173 onClick={prevStep}174 disabled={currentStep === 1}175 >176 <ChevronLeft className="w-4 h-4 mr-2" />177 Previous178 </Button>179 {currentStep < 3 ? (180 <Button onClick={nextStep}>181 Next182 <ChevronRight className="w-4 h-4 ml-2" />183 </Button>184 ) : (185 <Button onClick={() => alert('Form submitted!')}>186 Submit187 </Button>188 )}189 </div>190 </CardContent>191 </Card>192 );193}