A component that provides structure for form fields, handling label placement, validation messages, descriptions, and consistent spacing across forms.
class-variance-authority
1'use client'
2
3import * as React from 'react'
4import { ChevronDownIcon } from 'lucide-react'
5import { toast } from 'sonner'
6
7import { Button } from '@/components/ui/button'
8import { Calendar } from '@/components/ui/calendar'
9import { Checkbox } from '@/components/ui/checkbox'
10import {
11 Field,
12 FieldDescription,
13 FieldError,
14 FieldGroup,
15 FieldLabel,
16 FieldLegend,
17 FieldSeparator,
18 FieldSet,
19} from '@/components/ui/field'
20import { Input } from '@/components/ui/input'
21import { Label } from '@/components/ui/label'
22import {
23 Popover,
24 PopoverContent,
25 PopoverTrigger,
26} from '@/components/ui/popover'
27import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
28import {
29 Select,
30 SelectContent,
31 SelectItem,
32 SelectTrigger,
33 SelectValue,
34} from '@/components/ui/select'
35import { Textarea } from '@/components/ui/textarea'
36import TimePicker from '@/components/ui/time-picker'
37
38export function AccountSetupDemo() {
39 const [open, setOpen] = React.useState(false)
40 const [date, setDate] = React.useState<Date | undefined>(undefined)
41 const [time, setTime] = React.useState<string>('')
42 const [form, setForm] = React.useState({
43 fullname: '',
44 email: '',
45 accountType: '',
46 })
47 const [errors, setErrors] = React.useState<Record<string, string>>({})
48
49 const handleSubmit = (e: React.FormEvent) => {
50 e.preventDefault()
51 const newErrors: Record<string, string> = {}
52
53 if (!form.fullname.trim()) {
54 newErrors.fullname = 'Full Name is required.'
55 }
56
57 if (!form.email.trim()) {
58 newErrors.email = 'Email is required.'
59 } else if (!/\S+@\S+\.\S+/.test(form.email)) {
60 newErrors.email = 'Email is invalid.'
61 }
62
63 if (!form.accountType) {
64 newErrors.accountType = 'Please select an account type.'
65 }
66
67 if (!date) {
68 newErrors.date = 'Please select a date.'
69 }
70
71 if (!time) {
72 newErrors.time = 'Please select a time.'
73 }
74
75 setErrors(newErrors)
76
77 if (Object.keys(newErrors).length === 0) {
78 toast('Form saved successfully')
79 }
80 }
81 return (
82 <div className="w-full max-w-md">
83 <form onSubmit={handleSubmit}>
84 <FieldGroup>
85 <FieldSet>
86 <FieldLegend>User Information</FieldLegend>
87 <FieldDescription>
88 Provide basic information to set up your profile.
89 </FieldDescription>
90
91 <FieldGroup className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2">
92 <Field>
93 <FieldLabel htmlFor="user-fullname">
94 Full Name
95 </FieldLabel>
96 <Input
97 id="user-fullname"
98 placeholder="John Doe"
99 value={form.fullname}
100 onChange={(e) =>
101 setForm({
102 ...form,
103 fullname: e.target.value,
104 })
105 }
106 />
107 {errors.fullname && (
108 <FieldError>{errors.fullname}</FieldError>
109 )}
110 </Field>
111
112 <Field>
113 <FieldLabel htmlFor="user-email">
114 Email Address
115 </FieldLabel>
116 <Input
117 id="user-email"
118 placeholder="john@example.com"
119 type="email"
120 value={form.email}
121 onChange={(e) =>
122 setForm({
123 ...form,
124 email: e.target.value,
125 })
126 }
127 />
128 {errors.email && (
129 <FieldError>{errors.email}</FieldError>
130 )}
131 </Field>
132 </FieldGroup>
133 <FieldGroup>
134 <Field>
135 <FieldLabel htmlFor="user-bio">
136 Short Bio
137 </FieldLabel>
138 <Textarea
139 id="user-bio"
140 placeholder="Tell us something about yourself"
141 className="resize-none"
142 />
143 </Field>
144 </FieldGroup>
145 </FieldSet>
146
147 <FieldSeparator>Preferences</FieldSeparator>
148
149 <FieldSet>
150 <FieldLegend>Preferences</FieldLegend>
151
152 <FieldGroup className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2">
153 <Field>
154 <FieldLabel>Account Type</FieldLabel>
155 <RadioGroup
156 value={form.accountType}
157 onValueChange={(val) =>
158 setForm({ ...form, accountType: val })
159 }
160 >
161 <div className="flex items-center gap-2">
162 <RadioGroupItem value="personal" />
163 <Label>Personal</Label>
164 </div>
165 <div className="flex items-center gap-2">
166 <RadioGroupItem value="business" />
167 <Label>Business</Label>
168 </div>
169 </RadioGroup>
170 {errors.accountType && (
171 <FieldError>
172 {errors.accountType}
173 </FieldError>
174 )}
175 </Field>
176 <Field>
177 <Field orientation="horizontal">
178 <Checkbox
179 id="email-updates"
180 defaultChecked
181 />
182 <FieldLabel
183 htmlFor="email-updates"
184 className="font-normal"
185 >
186 Receive email updates
187 </FieldLabel>
188 </Field>
189
190 <Field>
191 <FieldLabel htmlFor="timezone-select">
192 Timezone
193 </FieldLabel>
194 <Select defaultValue="">
195 <SelectTrigger id="timezone-select">
196 <SelectValue placeholder="Select timezone" />
197 </SelectTrigger>
198 <SelectContent>
199 <SelectItem value="IST">
200 India (IST)
201 </SelectItem>
202 <SelectItem value="UTC">
203 UTC
204 </SelectItem>
205 <SelectItem value="EST">
206 EST
207 </SelectItem>
208 </SelectContent>
209 </Select>
210 </Field>
211 </Field>
212 </FieldGroup>
213 </FieldSet>
214
215 <FieldSeparator>Schedule</FieldSeparator>
216
217 <FieldSet>
218 <FieldLegend>Schedule Availability</FieldLegend>
219 <FieldDescription>
220 Let us know when you are available.
221 </FieldDescription>
222
223 <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2">
224 <Field>
225 <FieldLabel htmlFor="available-date">
226 Available Date
227 </FieldLabel>
228 <Popover open={open} onOpenChange={setOpen}>
229 <PopoverTrigger asChild>
230 <Button
231 variant="outline"
232 id="date"
233 className="text-primary border-border h-10 w-60 justify-between font-medium hover:bg-gray-100"
234 >
235 {date
236 ? date.toLocaleDateString()
237 : 'Select date'}
238 <ChevronDownIcon />
239 </Button>
240 </PopoverTrigger>
241 <PopoverContent
242 className="w-auto overflow-hidden border-0 p-0"
243 align="start"
244 side="bottom"
245 >
246 <Calendar
247 mode="single"
248 selected={date}
249 captionLayout="dropdown"
250 onSelect={(date) => {
251 setDate(date)
252 setOpen(false)
253 }}
254 />
255 </PopoverContent>
256 </Popover>
257 {errors.date && (
258 <FieldError>{errors.date}</FieldError>
259 )}
260 </Field>
261
262 <Field>
263 <FieldLabel htmlFor="available-time">
264 Available Time
265 </FieldLabel>
266 <TimePicker
267 id="available-time"
268 value={time}
269 onValueChange={(val: string) =>
270 setTime(val)
271 }
272 />
273 {errors.time && (
274 <FieldError>{errors.time}</FieldError>
275 )}
276 </Field>
277 </div>
278 </FieldSet>
279
280 <Field orientation="horizontal">
281 <Button type="submit">Save</Button>
282 <Button
283 type="button"
284 variant="outline"
285 onClick={() => {
286 setForm({
287 fullname: '',
288 email: '',
289 accountType: '',
290 })
291 setDate(undefined)
292 setTime('')
293 setErrors({})
294 }}
295 >
296 Reset
297 </Button>
298 </Field>
299 </FieldGroup>
300 </form>
301 </div>
302 )
303}
304Install the following dependencies:
1'use client'
2
3import { useState } from 'react'
4
5import { Button } from '@/components/ui/button'
6import {
7 Field,
8 FieldContent,
9 FieldDescription,
10 FieldGroup,
11 FieldLabel,
12 FieldLegend,
13 FieldSet,
14 FieldTitle,
15} from '@/components/ui/field'
16import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
17import { Slider } from '@/components/ui/slider'
18
19export function FieldModelConfigurator() {
20 const [performance, setPerformance] = useState([60])
21
22 return (
23 <div className="w-full max-w-md">
24 <FieldGroup>
25 <FieldSet>
26 <FieldLegend>Select AI Model</FieldLegend>
27 <FieldDescription>
28 Choose the model architecture for your workload.
29 </FieldDescription>
30
31 <RadioGroup defaultValue="transformer" className="mt-2">
32 <FieldLabel htmlFor="model-transformer">
33 <Field orientation="horizontal">
34 <FieldContent>
35 <FieldTitle>Transformer Model</FieldTitle>
36 <FieldDescription>
37 Best for NLP, chat, summarization &
38 generative tasks.
39 </FieldDescription>
40 </FieldContent>
41 <RadioGroupItem
42 value="transformer"
43 id="model-transformer"
44 />
45 </Field>
46 </FieldLabel>
47
48 <FieldLabel htmlFor="model-vision">
49 <Field orientation="horizontal">
50 <FieldContent>
51 <FieldTitle>Vision Model</FieldTitle>
52 <FieldDescription>
53 Ideal for images, OCR, and computer
54 vision pipelines.
55 </FieldDescription>
56 </FieldContent>
57 <RadioGroupItem
58 value="vision"
59 id="model-vision"
60 />
61 </Field>
62 </FieldLabel>
63 </RadioGroup>
64 </FieldSet>
65
66 <FieldSet>
67 <FieldTitle>Performance Level</FieldTitle>
68 <FieldDescription>
69 Adjust model performance. Current:{' '}
70 <span className="font-medium tabular-nums">
71 {performance[0]}%
72 </span>
73 </FieldDescription>
74
75 <Slider
76 value={performance}
77 onValueChange={setPerformance}
78 min={0}
79 max={100}
80 step={5}
81 className="mt-3 w-full"
82 aria-label="Performance Level"
83 />
84 </FieldSet>
85 <Field orientation="horizontal">
86 <Button type="submit">Save</Button>
87 <Button type="button" variant="outline">
88 Reset
89 </Button>
90 </Field>
91 </FieldGroup>
92 </div>
93 )
94}
95| Prop | Type | Default |
|---|---|---|
| variant | legend,label | legend |
| Prop | Type | Default |
|---|---|---|
| orientation | vertical,horizontal,responsive | vertical |
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | false |
| Prop | Type | Default |
|---|---|---|
| errors | Array<{ message?: string } | undefined> | - |