Form
React Hook Form helpers for typed fields, labels, and validation messaging.
npx shadcn@latest add @aavya/ui-form
One-time setup — add the @aavya registry to your components.json so branded dependencies resolve to Aavya (not stock shadcn):
"registries": { "@aavya": "https://registry-dev.aavya.com/r/{name}.json" }Or install by full URL: npx shadcn@latest add https://registry-dev.aavya.com/r/ui-form.json
Do
- Associate every control with a label using the Form primitives (FormField / FormLabel).
- Show inline, specific error messages via FormMessage and link them to their field.
- Group related fields and provide a single clear submit action.
Don’t
- Don't validate only on submit for long forms — give field-level feedback.
- Don't disable the submit button without explaining what's missing.
- Don't convey errors with color alone.
Role
Native form controls with label / description / error wiring (handled by the Form primitives).
Keyboard
Standard field navigation and submit. (handled)
Screen reader
Labels, descriptions, and errors are associated via aria-describedby / aria-invalid when you use FormField / FormLabel / FormMessage. You provide the content.
Focus
On an invalid submit, move focus to the first errored field — you provide this.
Gotcha
Errors must be programmatically linked (FormMessage) — a visual-only error near a field isn't announced.
npm
import { Form } from '@/components/ui/form'
export default function Example() {
return (
<div className="p-6">
<Form />
</div>
)
}
'use client'
import * as React from 'react'
import type * as LabelPrimitive from '@radix-ui/react-label'
import { Slot } from '@radix-ui/react-slot'
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues
} from 'react-hook-form'
import { cn } from '@/lib/utils'
import { Label } from '@/components/ui/label'
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error('useFormField sho