āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/akash3444/basecn/components/form-with-tanstack-form ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
This guide demonstrates how to build forms using @tanstack/react-form and zod. We'll use createFormHook to compose accessible forms.
zod or any other validation library.React.useId() for generating unique IDs.aria attributes to form fields based on states.zod but you can use anything you want.<form.AppForm>
<form.AppField name="...">
{(field) => (
<form.Item>
<field.Label />
<field.Control>
{ /* Your form field */}
</field.Control>
<field.Description />
<field.Message />
</form.Item>
)}
</form.AppField>
</form.AppForm>
const { useAppForm } = createFormHook()
const form = useAppForm({...})
<form.AppForm>
<form.AppField name="username">
{(field) => (
<form.Item>
<field.Label>Username</field.Label>
<field.Control>
<Input
placeholder="shadcn"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</field.Control>
<field.Description>This is your public display name.</field.Description>
<field.Message />
</form.Item>
)}
</form.AppField>
</form.AppForm>
Define the shape of your form using a Zod schema. You can read more about using Zod in the Zod documentation.
"use client"
// [!code highlight]
import { z } from "zod"
// [!code highlight:3]
const formSchema = z.object({
username: z.string().min(2).max(50),
})
Use the createFormHook function to create a form hook, then use the useAppForm hook to define your form with validation and submit handling.
"use client"
import { z } from "zod"
// [!code highlight]
import { createFormHook } from "@/components/ui/form";
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
// [!code highlight:2]
// 1. Create a form hook
const { useAppForm } = createFormHook()
export function ProfileForm() {
// [!code highlight:11]
// 2. Define your form.
const form = useAppForm({
defaultValues: { username: "" } as z.infer<typeof formSchema>,
validators: { onChange: formSchema },
// 3. Define a submit handler.
onSubmit({ value }) {
// Do something with the form values.
// ā
This will be type-safe and validated.
console.log(value);
},
})
}
Use the form.AppForm component to wrap your form.
"use client"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { createFormHook } 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.",
})
})
const { useAppForm } = createFormHook()
export function ProfileForm() {
// ...
// [!code highlight:34]
return (
<form.AppForm>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
className="space-y-8"
>
<form.AppField name="username">
{(field) => (
<form.Item>
<field.Label>Username</field.Label>
<field.Control>
<Input
placeholder="shadcn"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</field.Control>
<field.Description>
This is your public display name.
</field.Description>
<field.Message />
</form.Item>
)}
</form.AppField>
<Button type="submit">Submit</Button>
</form>
</form.AppForm>
);
}
Create a form hook using the `createFormHook` function imported from `@/components/ui/form`. Ensure this is defined outside of your component.
```tsx showLineNumbers
// [!code highlight]
const { useAppForm } = createFormHook()
export function ProfileForm() {
// ...
}
```
Use the `useAppForm` hook instead of `useForm` to define your form with validation and submit handling.
```tsx showLineNumbers
export function ProfileForm() {
// [!code ++:10]
const form = useAppForm({
defaultValues: { username: "" } as z.infer<typeof formSchema>,
validators: { onChange: formSchema },
onSubmit({ value }) {
// Do something with the form values.
// ā
This will be type-safe and validated.
console.log(value);
},
})
// [!code --:12]
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ā
This will be type-safe and validated.
console.log(values)
}
}
```
</Step>
<Step>
### Define a form
Define a form using the `<form.AppForm>` component.
```tsx showLineNumbers
export function ProfileForm() {
// ...
return (
// [!code ++:32]
<form.AppForm>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
className="space-y-8"
>
<form.AppField name="username">
{(field) => (
<form.Item>
<field.Label>Username</field.Label>
<field.Control>
<Input
placeholder="shadcn"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</field.Control>
<field.Description>
This is your public display name.
</field.Description>
<field.Message />
</form.Item>
)}
</form.AppField>
<Button type="submit">Submit</Button>
</form>
</form.AppForm>
// [!code --:21]
<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>
);
}
```
</Step>
</Steps>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā