Skip to content
Redpanda UI
Esc
↑↓navigate↵open⌘Jpreview
Made by shadcn
Edit on GitHub

Form

Building forms with React Hook Form and Zod.

Installation

bash
npx shadcn@latest add @redpanda/form
bash
pnpm dlx shadcn@latest add @redpanda/form
bash
yarn dlx shadcn@latest add @redpanda/form
bash
bunx shadcn@latest add @redpanda/form
bash
nubx shadcn@latest add @redpanda/form
bash
aube dlx shadcn@latest add @redpanda/form

When to use

Use this decision tree to determine when to use the Form components:

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.

components/example-form.tsx
'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.

components/example-form.tsx
'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 to learn more about controlled components.

Build your form

We can now use the <Form /> components to build our form.

components/example-form.tsx
'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.

Examples

Simple Form

Use SimpleFormField for a single control such as Input or Textarea. For compound cards, option groups and date popovers, compose FormField, FormItem and FormControl so the field’s label, ref and validation attributes reach the actual interactive control. Put FormControl around PopoverTrigger, not inside its render element: this lets the field supply the trigger ID that its label targets.

Both profile examples share one schema and concrete profile email, notification, date-of-birth and email-preference components. These live together in the profile example library; they are not generic field templates. Username and Bio deliberately demonstrate manual FormField composition in the full example and SimpleFormField in the simple example. Each option retains its own associated label, actual control ref and validation description.

The Bio field accepts 10–160 characters, and failed submission focuses the first invalid control. Use existing FormItem layout and spacing recipes instead of local spacing overrides. Horizontal rows use the standard spacing scale; layout="card-horizontal" owns switch-card alignment and padding.

Required fields

JSON Field

See the following links for more examples on how to use the <Form /> component with other components:

Credits

Recent changes

  • patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested baseline while caret semantics still allow any compatible release within the same major.#133
  • minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
  • minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
  • majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
  • minorv0.3.0Add theme-provider component to the registry with documentation and tests. Includes playground type improvements (export RegistryItem, remove as-const boilerplate) and docs site dark mode border color fix.#109
See full history →

Last updated on

Was this page helpful?