Forms & Inputs

Form controls for user input with React Hook Form integration.

Build validated forms using the Form component with React Hook Form and Zod. Wrap inputs in FormField and FormControl for automatic validation, error display, and accessibility. All form components support dark mode and keyboard navigation.

This guide is part of the UI Components documentation.

Definition: Form components are controlled input elements that integrate with React Hook Form for validation, state management, and error handling - providing consistent UX patterns across the application.

Form

The Form component wraps React Hook Form's FormProvider with integrated validation display.

import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@kit/ui/form';

Basic Form

'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { Button } from '@kit/ui/button';
const schema = z.object({
email: z.string().email(),
name: z.string().min(2),
});
function MyForm() {
const form = useForm({
resolver: zodResolver(schema),
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl
render={
<Input placeholder="you@example.com" {...field} />
}
/>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}

Input

Basic text input field.

import { Input } from '@kit/ui/input';
<Input placeholder="Enter text" />
<Input type="email" placeholder="Email address" />
<Input type="password" placeholder="Password" />
<Input disabled placeholder="Disabled input" />

Textarea

Multi-line text input.

import { Textarea } from '@kit/ui/textarea';
<Textarea placeholder="Enter description..." />
<Textarea rows={6} placeholder="Longer text area" />

Select

Dropdown select with search and keyboard navigation.

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kit/ui/select';
<Select onValueChange={handleChange} defaultValue="option1">
<SelectTrigger>
<SelectValue placeholder="Select option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>

With Form Field

<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>Category</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl
render={
<SelectTrigger>
<SelectValue placeholder="Select category" />
</SelectTrigger>
}
/>
<SelectContent>
<SelectItem value="work">Work</SelectItem>
<SelectItem value="personal">Personal</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>

Checkbox

Boolean checkbox control.

import { Checkbox } from '@kit/ui/checkbox';
<div className="flex items-center gap-2">
<Checkbox id="terms" />
<label htmlFor="terms">Accept terms and conditions</label>
</div>

Radio Group

Single selection from multiple options.

import { RadioGroup, RadioGroupItem } from '@kit/ui/radio-group';
<RadioGroup defaultValue="option1" onValueChange={handleChange}>
<div className="flex items-center gap-2">
<RadioGroupItem value="option1" id="r1" />
<label htmlFor="r1">Option 1</label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="option2" id="r2" />
<label htmlFor="r2">Option 2</label>
</div>
</RadioGroup>

Switch

Toggle switch for boolean values.

import { Switch } from '@kit/ui/switch';
<div className="flex items-center gap-2">
<Switch id="notifications" />
<label htmlFor="notifications">Enable notifications</label>
</div>
{/* Small size */}
<Switch size="sm" />

Input OTP

One-time password input for verification codes.

import { InputOTP, InputOTPGroup, InputOTPSlot } from '@kit/ui/input-otp';
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>

Label

Accessible form label.

import { Label } from '@kit/ui/label';
<Label htmlFor="email">Email address</Label>
<Input id="email" type="email" />

Field

Form field wrapper with label and description.

import { Field, FieldLabel, FieldDescription, FieldError } from '@kit/ui/field';
<Field>
<FieldLabel>Username</FieldLabel>
<Input placeholder="Enter username" />
<FieldDescription>Your unique username</FieldDescription>
<FieldError>Username is required</FieldError>
</Field>

Image Uploader

Drag-and-drop image upload with preview.

import { ImageUploader } from '@kit/ui/image-uploader';
<ImageUploader
value={imageUrl}
onValueChange={(url) => setImageUrl(url)}
>
<span>Drop image here or click to upload</span>
</ImageUploader>

Supports JPEG, PNG, GIF, WebP up to 5MB.

Common Pitfalls

  • Forgetting 'use client': Forms require client-side state. Add the directive at the top of your component file.
  • Wrong FormControl pattern: Use <FormControl render={<Input />} />, not <FormControl><Input /></FormControl>.
  • Missing zodResolver: Pass resolver: zodResolver(schema) to useForm() for Zod validation to work.
  • Not spreading field props: Always spread {...field} on your input to connect it to React Hook Form.
  • Select value type mismatch: Select values are always strings. Convert numbers in your schema or onValueChange handler.
  • Checkbox boolean handling: Checkboxes use checked prop, not value. Use onCheckedChange instead of onChange.

Frequently Asked Questions

How do I show validation errors?
Add FormMessage inside your FormItem. It automatically displays the error for that field when validation fails.
Can I use forms without React Hook Form?
Yes. Use the standalone Input, Select, and other components directly. You'll need to manage state and validation yourself.
How do I set default values?
Pass defaultValues to useForm(): useForm({ defaultValues: { email: '', name: '' } }). For async data, use reset() after fetching.
Why isn't my form submitting?
Check that your button has type='submit', your form has onSubmit={form.handleSubmit(onSubmit)}, and your schema matches your form fields.
How do I handle file uploads?
Use the ImageUploader component for images. For general files, use a controlled input type='file' outside of React Hook Form.

Next: Buttons & Actions →