# Pattern: Form

## Intent

The standard data-entry pattern for entity creation and editing. Uses `react-hook-form` + `zod` for validation, ensuring all error handling and field state is declarative and automatic.

---

## When to Use

- Creating or editing any entity (user, product, record, configuration)
- Settings pages with multiple configurable fields
- Multi-section data entry inside a `Dialog`, `Sheet`, or full page

---

## Required Stack

This pattern **mandates** the following packages:

- `react-hook-form` — form state management
- `zod` — schema-based validation
- `@hookform/resolvers` — `zodResolver` adapter

```bash
npm install react-hook-form zod @hookform/resolvers
```

---

## Anatomy

```
<Card>
  <CardHeader> Title + Description </CardHeader>
  <Form>                         ← react-hook-form context
    <form onSubmit={handleSubmit}>
      <CardContent>              ← grid layout for fields
        <FormField>              ← one per field
          <FormItem>
            <FormLabel />
            <FormControl>
              <Input />          ← or Textarea, Select, etc.
            </FormControl>
            <FormMessage />      ← auto-rendered error
          </FormItem>
        </FormField>
      </CardContent>
      <CardFooter>
        <Button type="submit" />
      </CardFooter>
    </form>
  </Form>
</Card>
```

---

## Full Pattern Code

```tsx
'use client'; // (only for Next.js)

import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import {
  Button,
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
  Input,
  Textarea,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from 'xertica-ui/ui';

// 1. Define the schema
const profileSchema = z.object({
  fullName: z.string().min(3, 'Full name must be at least 3 characters'),
  email: z.string().email('Invalid email format'),
  role: z.string({ required_error: 'Please select a role' }),
  bio: z.string().optional(),
});

type ProfileFormValues = z.infer<typeof profileSchema>;

export function ProfileForm() {
  const form = useForm<ProfileFormValues>({
    resolver: zodResolver(profileSchema),
    defaultValues: {
      fullName: '',
      email: '',
      bio: '',
    },
  });

  function onSubmit(values: ProfileFormValues) {
    console.log(values);
    // Call your API here
  }

  return (
    <Card className="w-full max-w-2xl mx-auto">
      <CardHeader>
        <CardTitle>Edit Profile</CardTitle>
        <CardDescription>Update your personal information and preferences.</CardDescription>
      </CardHeader>

      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)}>
          <CardContent className="grid gap-6 md:grid-cols-2">
            {/* Full-width field */}
            <FormField
              control={form.control}
              name="fullName"
              render={({ field }) => (
                <FormItem className="col-span-full">
                  <FormLabel>Full Name</FormLabel>
                  <FormControl>
                    <Input placeholder="John Doe" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* Half-width field */}
            <FormField
              control={form.control}
              name="email"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Email</FormLabel>
                  <FormControl>
                    <Input type="email" placeholder="john@example.com" {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* Select field */}
            <FormField
              control={form.control}
              name="role"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Role</FormLabel>
                  <Select onValueChange={field.onChange} defaultValue={field.value}>
                    <FormControl>
                      <SelectTrigger>
                        <SelectValue placeholder="Select a role" />
                      </SelectTrigger>
                    </FormControl>
                    <SelectContent>
                      <SelectItem value="admin">Administrator</SelectItem>
                      <SelectItem value="editor">Editor</SelectItem>
                      <SelectItem value="viewer">Viewer</SelectItem>
                    </SelectContent>
                  </Select>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* Textarea — always full width */}
            <FormField
              control={form.control}
              name="bio"
              render={({ field }) => (
                <FormItem className="col-span-full">
                  <FormLabel>Bio</FormLabel>
                  <FormControl>
                    <Textarea
                      placeholder="Tell us about yourself..."
                      className="resize-none"
                      {...field}
                    />
                  </FormControl>
                  <FormDescription>Optional. Displayed on your public profile.</FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />
          </CardContent>

          <CardFooter className="justify-end gap-2">
            <Button type="button" variant="ghost">
              Cancel
            </Button>
            <Button type="submit">Save Changes</Button>
          </CardFooter>
        </form>
      </Form>
    </Card>
  );
}
```

---

## Composition Rules

1. **Always use `react-hook-form` + `zod`** — never implement manual `useState` validation.
2. **Error messages appear automatically** via `<FormMessage />` — never render error `<span>` or `<p>` manually.
3. **Form layout**: Use `grid grid-cols-1 md:grid-cols-2 gap-6` for two-column forms.
4. **Long fields** (Textarea, rich selects) always get `col-span-full`.
5. **Short scalar fields** (text, number, date) occupy a single column.
6. **Submit actions** in full-page forms: right-aligned in `CardFooter`. In modals: also right-aligned.
7. **Cancel buttons** use `variant="ghost"` and come before the submit button.
8. **Wrap the `<form>` tag** inside the `<Form>` context component — this provides the `react-hook-form` context.
9. When using `<Select>` inside a form, always use `<FormControl>` wrapping the `<SelectTrigger>`.

---

## Related Patterns

- [CRUD Pattern](./crud.md) — The table view that opens this form in a Dialog or Sheet
- [Login Pattern](./login.md) — Simplified auth form

## Related Components

- [`Form`](../components/form.md) — Full reference for all form sub-components
- [`Input`](../components/input.md)
- [`Select`](../components/select.md)
- [`Textarea`](../components/textarea.md)
- [`Dialog`](../components/dialog.md) — When embedding forms in modals
