Form
React formZod-first form block built on React Hook Form. Wires field labels, descriptions, error messages, and validation together; bind a field once and the rest is automatic.
Also available for Vue ->Installation
$ pnpm dlx shadcn@latest add https://react.uipkge.dev/r/react/form.json$ npx shadcn@latest add https://react.uipkge.dev/r/react/form.json$ yarn dlx shadcn@latest add https://react.uipkge.dev/r/react/form.json$ bunx shadcn@latest add https://react.uipkge.dev/r/react/form.json
Or with the named registry:
npx shadcn@latest add @uipkge-react/form
Examples
Schema
Type aliases from this item's source — use them to shape the data you pass in.
FormItemContextValue type FormItemContextValue {
id: string
} Dependencies
Files (2)
-
components/ui/form/form.tsx 4.1 kB
'use client' import * as React from 'react' 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' // The canonical shadcn form: react-hook-form + zod + @hookform/resolvers. // `Form` is just FormProvider — wire your form with useForm({ resolver: // zodResolver(schema) }) and spread the returned methods. `FormField` is a // thin Controller wrapper that publishes the field name on context so the // label / control / description / message parts can derive their ids and // error state without prop-drilling. const Form = FormProvider type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, > = { name: TName } const FormFieldContext = React.createContext<FormFieldContextValue>( {} as FormFieldContextValue, ) function 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> ) } function 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 should be used within <FormField>') } const { id } = itemContext return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, } } type FormItemContextValue = { id: string } const FormItemContext = React.createContext<FormItemContextValue>( {} as FormItemContextValue, ) function FormItem({ className, ...props }: React.ComponentProps<'div'>) { const id = React.useId() return ( <FormItemContext.Provider value={{ id }}> <div data-uipkge="" data-slot="form-item" className={cn('grid gap-1.5', className)} {...props} /> </FormItemContext.Provider> ) } function FormLabel({ className, ...props }: React.ComponentProps<typeof Label>) { const { error, formItemId } = useFormField() return ( <Label data-uipkge="" data-slot="form-label" data-error={!!error} className={cn('data-[error=true]:text-destructive', className)} htmlFor={formItemId} {...props} /> ) } function FormControl({ ...props }: React.ComponentProps<typeof Slot>) { const { error, formItemId, formDescriptionId, formMessageId } = useFormField() return ( <Slot data-uipkge="" data-slot="form-control" id={formItemId} aria-describedby={ !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}` } aria-invalid={!!error} {...props} /> ) } function FormDescription({ className, ...props }: React.ComponentProps<'p'>) { const { formDescriptionId } = useFormField() return ( <p data-uipkge="" data-slot="form-description" id={formDescriptionId} className={cn('text-muted-foreground text-sm', className)} {...props} /> ) } function FormMessage({ className, ...props }: React.ComponentProps<'p'>) { const { error, formMessageId } = useFormField() const body = error ? String(error?.message ?? '') : props.children if (!body) { return null } return ( <p data-uipkge="" data-slot="form-message" id={formMessageId} className={cn('text-destructive text-sm', className)} {...props} > {body} </p> ) } export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField, } -
components/ui/form/index.ts 0.1 kB
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField, } from './form'
Raw manifest: https://react.uipkge.dev/r/react/form.json