Input Field
Text input component integrated with React Hook Form. Supports tooltips and InputGroup integration for addons and buttons.
npx shadcn@latest add https://shuip.plvo.dev/r/rhf-input-field.json
pnpm dlx shadcn@latest add https://shuip.plvo.dev/r/rhf-input-field.json
bun x shadcn@latest add https://shuip.plvo.dev/r/rhf-input-field.json
import { InfoIcon } from 'lucide-react';import type * as React from 'react';import type { FieldPath, FieldValues, UseFormRegisterReturn } from 'react-hook-form';import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group';import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';export interface InputFieldProps<T extends FieldValues> extends React.ComponentProps<typeof InputGroupInput> {register: UseFormRegisterReturn<FieldPath<T>>;label?: string;description?: string;tooltip?: React.ReactNode;}export function InputField<T extends FieldValues>({register,label,description,tooltip,...props}: InputFieldProps<T>) {return (<FormField{...register}render={({ field, fieldState }) => {return (<FormItem data-invalid={fieldState.invalid}>{label && <FormLabel>{label}</FormLabel>}<FormControl><InputGroup><InputGroupInput {...field} type='text' aria-invalid={fieldState.invalid} {...props} />{tooltip && (<InputGroupAddon align='inline-end'><Tooltip><TooltipTrigger asChild><InputGroupButton aria-label='Info' size='icon-xs'><InfoIcon /></InputGroupButton></TooltipTrigger><TooltipContent>{tooltip}</TooltipContent></Tooltip></InputGroupAddon>)}</InputGroup></FormControl><FormMessage className='text-xs text-left' />{description && <FormDescription className='text-xs'>{description}</FormDescription>}</FormItem>);}}/>);}
Loading...
InputField is a text input component that encapsulates React Hook Form's field management with shadcn/ui's design system. It handles all the boilerplate of connecting form state to an input element: wiring event handlers, displaying errors, managing touched states, and rendering consistent UI.
This component is useful when you want to quickly add form inputs without manually setting up FormField render props, input bindings, and error display logic for every field.
Built-in features
- Type-safe registration: Uses
registerfrom React Hook Form for validation - Tooltip integration: Optional InfoIcon button with tooltip content via
tooltipprop - InputGroup ready: Built on shadcn InputGroup for seamless addon integration
- Zod validation: Native integration with react-hook-form and Zod schemas
- Full type inference: Field value types automatically inferred from form schema
Less boilerplate
React Hook Form's standard approach uses render props to access field state:
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="your@email.com" {...field} />
</FormControl>
<FormDescription>
Your email address
</FormDescription>
<FormMessage />
</FormItem>
)}
/>With InputField, this reduces to a single declarative component:
<InputField
register={form.register('email')}
label="Email"
description="Your email address"
placeholder="your@email.com"
/>Examples
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 { SubmitButton } from '@/components/ui/shuip/submit-button';const zodSchema = z.object({email: z.email({ message: 'Invalid email' }),});export default function InputFieldExample() {const form = useForm({defaultValues: { email: '' },resolver: zodResolver(zodSchema),});async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(`Hello ${values.email}`);} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><InputFieldregister={form.register('email')}type='email'label='Email'description='Your email'placeholder='john@example.com'/><SubmitButton>Check</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 { InputField } from '@/components/ui/shuip/react-hook-form/input-field';import { SubmitButton } from '@/components/ui/shuip/submit-button';const zodSchema = z.object({name: z.string().nonempty({ message: 'Name is required' }),});export default function InputFieldExample() {const form = useForm({defaultValues: { name: '' },resolver: zodResolver(zodSchema),});async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(`Hello ${values.name}`);} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><InputField register={form.register('name')} label='Name' description='Your name' placeholder='John' /><SubmitButton>Check</SubmitButton></form></Form>);}
Tooltip
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 { SubmitButton } from '@/components/ui/shuip/submit-button';const zodSchema = z.object({name: z.string().nonempty({ message: 'Name is required' }),});export default function InputFieldExample() {const form = useForm({defaultValues: { name: '' },resolver: zodResolver(zodSchema),});async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(`Hello ${values.name}`);} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><InputFieldregister={form.register('name')}label='Name'description='Your name'placeholder='John'tooltip='This is a tooltip where you can put some text'/><SubmitButton>Check</SubmitButton></form></Form>);}
Validation
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 { SubmitButton } from '@/components/ui/shuip/submit-button';const zodSchema = z.object({username: z.string().min(3, 'Username must be at least 3 characters').regex(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores allowed'),email: z.string().email('Invalid email format'),age: z.coerce.number().min(18, 'Must be at least 18 years old').max(120, 'Age must be realistic'),});export default function RhfInputFieldValidationExample() {const form = useForm({defaultValues: { username: '', email: '', age: '' },resolver: zodResolver(zodSchema),});async function onSubmit(values: z.infer<typeof zodSchema>) {try {alert(`User: ${values.username}\nEmail: ${values.email}\nAge: ${values.age}`);} catch (error) {console.error(error);}}return (<Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'><InputFieldregister={form.register('username')}label='Username'description='Username will be used for your profile'placeholder='Enter username'/><InputFieldregister={form.register('email')}type='email'label='Email'description='We will send confirmation to this email'placeholder='your@email.com'/><InputFieldregister={form.register('age')}type='number'label='Age'description='You must be 18 or older to register'placeholder='25'/><SubmitButton>Register</SubmitButton></form></Form>);}
Props
Prop
Type