mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-22 12:15:43 +00:00
242 lines
5.9 KiB
Plaintext
242 lines
5.9 KiB
Plaintext
---
|
|
title: Form
|
|
description: Building forms with React Hook Form and Zod.
|
|
links:
|
|
doc: https://react-hook-form.com
|
|
---
|
|
|
|
import { InfoIcon } from "lucide-react"
|
|
|
|
<Callout icon={<InfoIcon />} title="We are not actively developing this component anymore.">
|
|
|
|
The Form component is an abstraction over the `react-hook-form` library. Going forward, we recommend using the [`<Field />`](/docs/components/field) component to build forms. See the [Form](/docs/forms) documentation for more information.
|
|
|
|
</Callout>
|
|
|
|
Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.
|
|
|
|
Well-designed HTML forms are:
|
|
|
|
- Well-structured and semantically correct.
|
|
- Easy to use and navigate (keyboard).
|
|
- Accessible with ARIA attributes and proper labels.
|
|
- Has support for client and server side validation.
|
|
- Well-styled and consistent with the rest of the application.
|
|
|
|
In this guide, we will take a look at building forms with [`react-hook-form`](https://react-hook-form.com/) and [`zod`](https://zod.dev). We're going to use a `<FormField>` component to compose accessible forms using Radix UI components.
|
|
|
|
## Features
|
|
|
|
The `<Form />` component is a wrapper around the `react-hook-form` library. It provides a few things:
|
|
|
|
- Composable components for building forms.
|
|
- A `<FormField />` component for building controlled form fields.
|
|
- Form validation using `zod`.
|
|
- Handles accessibility and error messages.
|
|
- Uses `React.useId()` for generating unique IDs.
|
|
- Applies the correct `aria` attributes to form fields based on states.
|
|
- Built to work with all Radix UI components.
|
|
- Bring your own schema library. We use `zod` but you can use anything you want.
|
|
- **You have full control over the markup and styling.**
|
|
|
|
## Anatomy
|
|
|
|
```tsx showLineNumbers
|
|
<Form>
|
|
<FormField
|
|
control={...}
|
|
name="..."
|
|
render={() => (
|
|
<FormItem>
|
|
<FormLabel />
|
|
<FormControl>
|
|
{ /* Your form field */}
|
|
</FormControl>
|
|
<FormDescription />
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</Form>
|
|
```
|
|
|
|
## Example
|
|
|
|
```tsx showLineNumbers
|
|
const form = useForm()
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Username</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="shadcn" {...field} />
|
|
</FormControl>
|
|
<FormDescription>This is your public display name.</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
```
|
|
|
|
## Installation
|
|
|
|
<CodeTabs>
|
|
|
|
<TabsList>
|
|
<TabsTrigger value="cli">CLI</TabsTrigger>
|
|
<TabsTrigger value="manual">Manual</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="cli">
|
|
|
|
<Steps>
|
|
|
|
### Command
|
|
|
|
```bash
|
|
npx shadcn@latest add form
|
|
```
|
|
|
|
</Steps>
|
|
|
|
</TabsContent>
|
|
|
|
<TabsContent value="manual">
|
|
|
|
<Steps>
|
|
|
|
<Step>Install the following dependencies:</Step>
|
|
|
|
```bash
|
|
npm install @radix-ui/react-label @radix-ui/react-slot react-hook-form @hookform/resolvers zod
|
|
```
|
|
|
|
<Step>Copy and paste the following code into your project.</Step>
|
|
|
|
<ComponentSource name="form" title="components/ui/form.tsx" />
|
|
|
|
<Step>Update the import paths to match your project setup.</Step>
|
|
|
|
</Steps>
|
|
|
|
</TabsContent>
|
|
|
|
</CodeTabs>
|
|
|
|
## Usage
|
|
|
|
### Create a form schema
|
|
|
|
Define the shape of your form using a Zod schema. You can read more about using Zod in the [Zod documentation](https://zod.dev).
|
|
|
|
```tsx showLineNumbers title="components/example-form.tsx" {3,5-7}
|
|
"use client"
|
|
|
|
import { z } from "zod"
|
|
|
|
const formSchema = z.object({
|
|
username: z.string().min(2).max(50),
|
|
})
|
|
```
|
|
|
|
### Define a form
|
|
|
|
Use the `useForm` hook from `react-hook-form` to create a form.
|
|
|
|
```tsx showLineNumbers title="components/example-form.tsx" {3-4,14-20,22-27}
|
|
"use client"
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { useForm } from "react-hook-form"
|
|
import { z } from "zod"
|
|
|
|
const formSchema = z.object({
|
|
username: z.string().min(2, {
|
|
message: "Username must be at least 2 characters.",
|
|
}),
|
|
})
|
|
|
|
export function ProfileForm() {
|
|
// 1. Define your form.
|
|
const form = useForm<z.infer<typeof formSchema>>({
|
|
resolver: zodResolver(formSchema),
|
|
defaultValues: {
|
|
username: "",
|
|
},
|
|
})
|
|
|
|
// 2. Define a submit handler.
|
|
function onSubmit(values: z.infer<typeof formSchema>) {
|
|
// Do something with the form values.
|
|
// ✅ This will be type-safe and validated.
|
|
console.log(values)
|
|
}
|
|
}
|
|
```
|
|
|
|
Since `FormField` is using a controlled component, you need to provide a default value for the field. See the [React Hook Form docs](https://react-hook-form.com/docs/usecontroller) to learn more about controlled components.
|
|
|
|
### Build your form
|
|
|
|
We can now use the `<Form />` components to build our form.
|
|
|
|
```tsx showLineNumbers title="components/example-form.tsx" {7-17,28-50}
|
|
"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 function ProfileForm() {
|
|
// ...
|
|
|
|
return (
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
|
<FormField
|
|
control={form.control}
|
|
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>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Done
|
|
|
|
That's it. You now have a fully accessible form that is type-safe with client-side validation.
|