šŸ“ Sign Up | šŸ” Log In

← Root | ↑ Up

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ šŸ“„ shadcn/directory/akash3444/basecn/components/form-with-tanstack-form │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

╔══════════════════════════════════════════════════════════════════════════════════════════════╗
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘

title: Form (TanStack Form) description: Building forms with TanStack Form and Zod. links: doc: https://tanstack.com/form

<ComponentPreview name="form-tanstack-demo" />

This guide demonstrates how to build forms using @tanstack/react-form and zod. We'll use createFormHook to compose accessible forms.

Features

  • Composable components for building forms.
  • Built-in field components for building controlled form fields.
  • Form validation using zod or any other validation library.
  • Handles accessibility and error messages.
  • Uses React.useId() for generating unique IDs.
  • Applies the correct aria attributes to form fields based on states.
  • 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

<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>

Example

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>

Installation

<CodeBlockCommand component="form-tanstack" />

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.

"use client"

// [!code highlight]
import { z } from "zod"

// [!code highlight:3]
const formSchema = z.object({
  username: z.string().min(2).max(50),
})

Define a form

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);
    },
  })
}

Build your form

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>
  );
}

Examples

Checkbox Form

<ComponentPreview name="checkbox-tanstack-form" />

Radio Form

<ComponentPreview name="radio-tanstack-form" />

Select Form

<ComponentPreview name="select-tanstack-form" />

Switch Form

<ComponentPreview name="switch-tanstack-form" />

Textarea Form

<ComponentPreview name="textarea-tanstack-form" />

Combobox Form

<ComponentPreview name="combobox-tanstack-form" />

Date Picker Form

<ComponentPreview name="date-picker-tanstack-form" />

Migrating from React Hook Form

<Steps> <Step> ### Define a form hook
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>
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•

← Root | ↑ Up