# React Hook Form + Zod

> Controller binds the combobox to RHF; Zod resolver drives FieldError on submit.

A shadcn/ui block from the [@flx](https://ui.flexnative.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/flx/command-12).

- Page, with a live preview: https://shadcn.exchange/r/flx/command-12
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/flx/command-12-light-57391b4b807383ac.png
- Registry item: https://ui.flexnative.com/r/command-12.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.flexnative.com/r/command-12.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@hookform/resolvers`, `react-hook-form`, `sonner`, `zod`
- Registry items: `popover`, `command`, `button`, `field`

## Files

### `registry/patterns/command/command-12.tsx`

```tsx
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'

import { Button } from '@/components/ui/button'
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command'
import {
  Field,
  FieldError,
  FieldGroup,
  FieldLabel,
} from '@/components/ui/field'
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover'
import { cn } from '@/lib/utils'
import { ChevronsUpDownIcon } from 'lucide-react'
import { useState } from 'react'

const frameworks = [
  { value: 'next', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
] as const

const formSchema = z.object({
  framework: z.string().min(1, 'Select a framework.'),
})

type FormValues = z.infer<typeof formSchema>

export function Command12() {
  const [open, setOpen] = useState(false)
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      framework: '',
    },
  })

  function onSubmit(data: FormValues) {
    toast.success('Submitted', {
      description: frameworks.find((item) => item.value === data.framework)
        ?.label,
    })
  }

  return (
    <form
      id="command-rhf-form"
      onSubmit={form.handleSubmit(onSubmit)}
      className="flex w-full max-w-sm flex-col gap-4"
    >
      <FieldGroup>
        <Controller
          name="framework"
          control={form.control}
          render={({ field, fieldState }) => (
            <Field data-invalid={fieldState.invalid}>
              <FieldLabel htmlFor="command-rhf-framework">Framework</FieldLabel>
              <Popover open={open} onOpenChange={setOpen}>
                <PopoverTrigger
                  render={
                    <Button
                      id="command-rhf-framework"
                      type="button"
                      variant="outline"
                      role="combobox"
                      aria-expanded={open}
                      aria-invalid={fieldState.invalid}
                      className="w-full justify-between font-normal"
                    >
                      <span
                        className={cn(!field.value && 'text-muted-foreground')}
                      >
                        {field.value
                          ? frameworks.find(
                              (item) => item.value === field.value,
                            )?.label
                          : 'Select framework'}
                      </span>
                      <ChevronsUpDownIcon className="text-muted-foreground" />
                    </Button>
                  }
                />
                <PopoverContent
                  className="w-(--anchor-width) p-0"
                  align="start"
                >
                  <Command>
                    <CommandInput placeholder="Search framework..." />
                    <CommandList>
                      <CommandEmpty>No framework found.</CommandEmpty>
                      <CommandGroup>
                        {frameworks.map((item) => (
                          <CommandItem
                            key={item.value}
                            value={item.value}
                            data-checked={field.value === item.value}
                            onSelect={(current) => {
                              field.onChange(
                                current === field.value ? '' : current,
                              )
                              setOpen(false)
                            }}
                          >
                            {item.label}
                          </CommandItem>
                        ))}
                      </CommandGroup>
                    </CommandList>
                  </Command>
                </PopoverContent>
              </Popover>
              {fieldState.invalid && <FieldError errors={[fieldState.error]} />}
            </Field>
          )}
        />
      </FieldGroup>
      <Button type="submit" size="sm">
        Submit
      </Button>
    </form>
  )
}
```
