React form component with type-safe validation using React Hook Form and Zod. Built with TypeScript and Tailwind CSS for Next.js applications.
Building forms in React used to suck. This shadcn/ui setup combines React Hook Form with Zod validation so you get type safety, error handling, and all that good stuff without the usual headaches in your React apps.
A clean, accessible form with real-time validation:
Loading component...
"use client"import { zodResolver } from "@hookform/resolvers/zod"import { useForm } from "react-hook-form"import { z } from "zod"import { Button } from "~/components/ui/button"import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,} from "~/components/ui/form"import { Input } from "~/components/ui/input"const formSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters.", }),})export default function FormDemo() { const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { username: "", }, }) function onSubmit(values: z.infer<typeof formSchema>) { console.log(values) } return ( <Form {...(form as any)}> <form className="space-y-8" onSubmit={form.handleSubmit(onSubmit)}> <FormField control={form.control as any} name="username" render={({ field }) => ( <FormItem> <FormLabel>Username</FormLabel> <FormControl> <Input placeholder="shadcn" {...field} /> </FormControl> <FormDescription>This is your public display name.</FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> )}
React Hook Form handles the state, Zod catches the errors, and the accessibility stuff happens automatically. Styled with Tailwind CSS so it matches your design system instead of looking like basic HTML forms.
"use client"import { zodResolver } from "@hookform/resolvers/zod"import { useForm } from "react-hook-form"import { z } from "zod"import { Button } from "~/components/ui/button"import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,} from "~/components/ui/form"import { Input } from "~/components/ui/input"import { Textarea } from "~/components/ui/textarea"const profileFormSchema = z.object({ username: z .string() .min(2, { message: "Username must be at least 2 characters.", }) .max(30, { message: "Username must not be longer than 30 characters.", }), email: z .string({ required_error: "Please select an email to display.", }) .email(), bio: z.string().max(160).min(4), urls: z .array( z.object({ value: z.string().url({ message: "Please enter a valid URL." }), }), ) .optional(),})type ProfileFormValues = z.infer<typeof profileFormSchema>export default function ProfileForm() { const form = useForm<ProfileFormValues>({ resolver: zodResolver(profileFormSchema), defaultValues: { username: "", email: "", bio: "", }, mode: "onChange", }) function onSubmit(data: ProfileFormValues) { console.log(data) } return ( <Form {...(form as any)}> <form className="w-2/3 space-y-6" onSubmit={form.handleSubmit(onSubmit)}> <FormField control={form.control as any} name="username" render={({ field }) => ( <FormItem> <FormLabel>Username</FormLabel> <FormControl> <Input placeholder="shadcn" {...field} /> </FormControl> <FormDescription> This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days. </FormDescription> <FormMessage /> </FormItem> )} /> <FormField control={form.control as any} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input placeholder="[email protected]" type="email" {...field} /> </FormControl> <FormDescription> You can manage verified email addresses in your email settings. </FormDescription> <FormMessage /> </FormItem> )} /> <FormField control={form.control as any} name="bio" render={({ field }) => ( <FormItem> <FormLabel>Bio</FormLabel> <FormControl> <Textarea className="resize-none" placeholder="Tell us a little bit about yourself" {...field} /> </FormControl> <FormDescription> You can <span>@mention</span> other users and organizations to link to them. </FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Update profile</Button> </form> </Form> )}
import { z } from "zod"const formSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters.", }), email: z.string().email({ message: "Please enter a valid email address.", }),})
Validate early, validate often. This free shadcn/ui form gives you multiple validation modes—use them wisely. Show errors after users finish typing, not on every keystroke. This TypeScript component handles the timing—you provide feedback that helps instead of annoys users in your Next.js applications.
Write schemas that match reality. Zod catches impossible data, but it can't catch business logic. Email addresses need @ symbols, but they also need to be real addresses users can access. This open source shadcn component validates format—you validate meaning.
Handle loading states properly. Show spinners, disable buttons, give feedback during submission. Users will click "Submit" repeatedly if nothing happens. Your JavaScript form should respond immediately, even if processing takes time in your React applications.
Group related fields logically. Long forms overwhelm users. Use visual grouping, clear sections, and maybe multiple steps. This Tailwind CSS component handles any layout—you provide organization that matches user mental models.
Make errors actionable. "Invalid input" tells users nothing. "Username must be at least 3 characters" tells them exactly how to fix it. Clear error messages reduce support tickets and improve conversion rates.
Forms naturally work with Input, Select, and Textarea components for data collection in your React applications. Use Button components for consistent action styling across all your forms.
For complex forms, combine with Tabs components to organize multi-step processes or Dialog components for modal forms. DatePicker and Checkbox components integrate seamlessly with the form validation system. This open source pattern keeps your forms consistent and accessible.
When building settings panels, pair forms with Switch components for toggles or RadioGroup for single selections. Alert components work well for showing form-level errors or success messages. Your JavaScript application can compose these components while maintaining proper validation.
For data tables, use forms with DataTable components for bulk actions or filtering. The form provides validation—other shadcn components handle specific input types and interactions.