# Forms Guide **Master form patterns with react-hook-form + Zod validation**: Learn field layouts, error handling, loading states, and accessibility best practices for bulletproof forms. --- ## Table of Contents 1. [Form Architecture](#form-architecture) 2. [Basic Form Pattern](#basic-form-pattern) 3. [Field Patterns](#field-patterns) 4. [Validation with Zod](#validation-with-zod) 5. [Error Handling](#error-handling) 6. [Loading & Submit States](#loading--submit-states) 7. [Form Layouts](#form-layouts) 8. [Advanced Patterns](#advanced-patterns) --- ## Form Architecture ### Technology Stack - **react-hook-form** - Form state management, validation - **Zod** - Schema validation - **@hookform/resolvers** - Zod resolver for react-hook-form - **shadcn/ui components** - Input, Label, Button, etc. **Why this stack?** - ✅ Type-safe validation (TypeScript + Zod) - ✅ Minimal re-renders (react-hook-form) - ✅ Accessible by default (shadcn/ui) - ✅ Easy error handling - ✅ Built-in loading states --- ### Form Decision Tree ``` Need a form? │ ├─ Single field (search, filter)? │ └─> Use uncontrolled input with onChange │ setQuery(e.target.value)} /> │ ├─ Simple form (1-3 fields, no complex validation)? │ └─> Use react-hook-form without Zod │ const form = useForm(); │ └─ Complex form (4+ fields, validation, async submit)? └─> Use react-hook-form + Zod const form = useForm({ resolver: zodResolver(schema) }); ``` --- ## Basic Form Pattern ### Minimal Form (No Validation) ```tsx import { useForm } from 'react-hook-form'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; interface FormData { email: string; } export function SimpleForm() { const form = useForm(); const onSubmit = (data: FormData) => { console.log(data); }; return (
); } ``` --- ### Complete Form Pattern (with Zod) ```tsx import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; // 1. Define validation schema const formSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters'), }); // 2. Infer TypeScript type from schema type FormData = z.infer; export function LoginForm() { // 3. Initialize form with Zod resolver const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { email: '', password: '', }, }); // 4. Submit handler (type-safe!) const onSubmit = async (data: FormData) => { try { await loginUser(data); toast.success('Logged in successfully'); } catch (error) { toast.error('Invalid credentials'); } }; return (
{/* Email field */}
{form.formState.errors.email && (

{form.formState.errors.email.message}

)}
{/* Password field */}
{form.formState.errors.password && (

{form.formState.errors.password.message}

)}
{/* Submit button */}
); } ``` **Key points:** 1. Define Zod schema first 2. Infer TypeScript type with `z.infer` 3. Use `zodResolver` in `useForm` 4. Register fields with `{...form.register('fieldName')}` 5. Show errors from `form.formState.errors` 6. Disable submit during submission --- ## Field Patterns ### Text Input ```tsx
{form.formState.errors.name && (

{form.formState.errors.name.message}

)}
``` --- ### Textarea ```tsx