Select Field
Select dropdown component integrated with React Hook Form for single-choice selections from key-value option pairs.
npx shadcn@latest add https://shuip.plvo.dev/r/rhf-select-field.json
pnpm dlx shadcn@latest add https://shuip.plvo.dev/r/rhf-select-field.json
bun x shadcn@latest add https://shuip.plvo.dev/r/rhf-select-field.json
import type { SelectProps } from '@radix-ui/react-select';import type { FieldPath, FieldValues, UseFormRegisterReturn } from 'react-hook-form';import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';/*** Key is the label, value is the value* @example* const options: SelectFieldOption = {* 'First': '1',* 'Second': '2',* 'Third': '3',* };*/export type SelectFieldOption = Record<string, string>;export interface SelectFieldProps<TFieldValues extends FieldValues> extends SelectProps {register: UseFormRegisterReturn<FieldPath<TFieldValues>>;options: SelectFieldOption;label?: string;placeholder?: string;description?: string;defaultValue?: TFieldValues[FieldPath<TFieldValues>];}export function SelectField<TFieldValues extends FieldValues>({register,options,label,description,placeholder,defaultValue,...props}: SelectFieldProps<TFieldValues>) {return (<FormField{...register}defaultValue={defaultValue}render={({ field, fieldState }) => (<FormItem data-invalid={fieldState.invalid}>{label && <FormLabel>{label}</FormLabel>}<Select defaultValue={field.value} onValueChange={field.onChange} {...props}><FormControl><SelectTrigger aria-invalid={fieldState.invalid} className='w-full'><SelectValue placeholder={placeholder} /></SelectTrigger></FormControl><SelectContent>{Object.entries(options).map(([label, value]) => (<SelectItem key={label} value={value}>{label}</SelectItem>))}</SelectContent></Select><FormMessage className='text-xs text-left' />{description && <FormDescription className='text-xs'>{description}</FormDescription>}</FormItem>)}/>);}
Loading...
Select dropdowns provide single-choice selection from a list of options with labels and values. SelectField automatically renders select options from a key-value object, where keys become display labels and values become form data.
The component handles the complexity of connecting Radix UI's Select with React Hook Form, including proper value binding, change handlers, and validation state management.
Built-in features
- Automatic option rendering: Pass a key-value object and get a fully functional select dropdown
- Label-value mapping: Keys display to users, values are stored in form data
- Placeholder support: Optional placeholder text for unselected state
- Zod validation: Native integration with react-hook-form and Zod schemas
- Accessibility support: Full keyboard navigation and screen reader compatibility
Less boilerplate
<FormField
control={form.control}
name="country"
render={({ field }) => (
<FormItem>
<FormLabel>Country</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a country" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
<SelectItem value="fr">France</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Choose your country
</FormDescription>
<FormMessage />
</FormItem>
)}
/>With SelectField, this reduces to a single declarative component:
<SelectField
register={form.register('country')}
label="Country"
placeholder="Select a country"
description="Choose your country"
options={{ 'United States': 'us', 'Canada': 'ca', 'France': 'fr' }}
/>Examples
Conditional
Loading...
'use client';import { zodResolver } from '@hookform/resolvers/zod';import { useForm } from 'react-hook-form';import { z } from 'zod';import { Form } from '@/components/ui/form';import { InputField } from '@/components/ui/shuip/react-hook-form/input-field';import { SelectField } from '@/components/ui/shuip/react-hook-form/select-field';import { SubmitButton } from '@/components/ui/shuip/submit-button';const zodSchema = z.object({accountType: z.enum(['personal', 'business']),businessName: z.string().optional(),companySize: z.string().optional(),});export default function RhfSelectFieldConditionalExample() {const form = useForm({defaultValues: {accountType: undefined,businessName: '',companySize: '',},resolver: zodResolver(zodSchema),});const accountType = form.watch('accountType');async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(JSON.stringify(values, null, 2));} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><SelectFieldregister={form.register('accountType')}options={{Personal: 'personal',Business: 'business',}}label='Account Type'placeholder='Select account type'description='Choose the type of account you want to create'/>{/* Show business fields only when Business is selected */}{accountType === 'business' && (<><InputFieldregister={form.register('businessName')}label='Business Name'placeholder='Enter your business name'description='Legal name of your business'/><SelectFieldregister={form.register('companySize')}options={{'1-10 employees': '1-10','11-50 employees': '11-50','51-200 employees': '51-200','201-1000 employees': '201-1000','1000+ employees': '1000+',}}label='Company Size'placeholder='Select company size'description='Number of employees in your company'/></>)}<SubmitButton>Create Account</SubmitButton></form></Form>);}
Dependent
Loading...
'use client';import { zodResolver } from '@hookform/resolvers/zod';import { useEffect, useState } from 'react';import { useForm } from 'react-hook-form';import { z } from 'zod';import { Form } from '@/components/ui/form';import { SelectField } from '@/components/ui/shuip/react-hook-form/select-field';import { SubmitButton } from '@/components/ui/shuip/submit-button';const COUNTRIES = {'United States': 'us',Canada: 'ca',Mexico: 'mx',};const STATES_BY_COUNTRY: Record<string, Record<string, string>> = {us: {California: 'ca',Texas: 'tx','New York': 'ny',Florida: 'fl',},ca: {Ontario: 'on',Quebec: 'qc','British Columbia': 'bc',Alberta: 'ab',},mx: {'Mexico City': 'cdmx',Jalisco: 'jal','Nuevo León': 'nl',},};const zodSchema = z.object({country: z.string().min(1, 'Please select a country'),state: z.string().min(1, 'Please select a state'),});export default function RhfSelectFieldDependentExample() {const [stateOptions, setStateOptions] = useState<Record<string, string>>({});const form = useForm({defaultValues: {country: '',state: '',},resolver: zodResolver(zodSchema),});const country = form.watch('country');useEffect(() => {if (country && STATES_BY_COUNTRY[country]) {setStateOptions(STATES_BY_COUNTRY[country]);} else {setStateOptions({});}// Reset state field when country changesform.setValue('state', '');}, [country, form]);async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(JSON.stringify(values, null, 2));} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><SelectFieldregister={form.register('country')}options={COUNTRIES}label='Country'placeholder='Select a country'description='Choose your country'/><SelectFieldregister={form.register('state')}options={stateOptions}label='State / Province'placeholder={Object.keys(stateOptions).length === 0 ? 'Select a country first' : 'Select a state'}description='Choose your state or province'/><SubmitButton>Submit</SubmitButton></form></Form>);}
Default
Loading...
'use client';import { zodResolver } from '@hookform/resolvers/zod';import { useForm } from 'react-hook-form';import { z } from 'zod';import { Form } from '@/components/ui/form';import { SelectField, type SelectFieldOption } from '@/components/ui/shuip/react-hook-form/select-field';import { SubmitButton } from '@/components/ui/shuip/submit-button';const options: SelectFieldOption = {First: '1',Second: '2',Third: '3',Fourth: '4',};const zodSchema = z.object({selection: z.enum(Object.values(options) as [string]),});export default function SelectFieldExample() {const form = useForm({defaultValues: {selection: '1',},resolver: zodResolver(zodSchema),});async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(`Selection: ${values.selection}`);} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><SelectFieldregister={form.register('selection')}placeholder='Select an option'label='selection'options={options}defaultValue={'3'}/><SubmitButton>Check</SubmitButton></form></Form>);}
Props
Prop
Type