Forms
Form Field
Accessible labels, descriptions and validation wiring.
Form Field
Accessible labels, descriptions and validation wiring.
Shown in your workspace navigation.
Choose a name with at least three characters.
Install
npx shadcn@latest add http://localhost:3000/r/form-field.jsonDependencies: label
Usage
"use client"
import { FormField, FormFieldControl, FormFieldDescription, FormFieldError, FormFieldLabel } from "@/components/ui/form-field"
import { Input } from "@/components/ui/input"
export function Example() {
return (
<FormField id="project-name" invalid className="max-w-xs">
<FormFieldLabel>Project name</FormFieldLabel>
<FormFieldControl><Input defaultValue="pepeloper/ui" /></FormFieldControl>
<FormFieldDescription>Shown in your workspace.</FormFieldDescription>
<FormFieldError>Choose a different name.</FormFieldError>
</FormField>
)
}API
FormFieldFormFieldControlFormFieldDescriptionFormFieldErrorFormFieldLabeluseFormFieldtype FormFieldContextValue = {
controlId: string
descriptionId: string
errorId: string
invalid: boolean
}
function FormField(props: React.ComponentProps<"div"> & { id?: string; invalid?: boolean })
function FormFieldLabel(props: React.ComponentProps<typeof Label>)
function FormFieldControl(props: { children: React.ReactElement })
function FormFieldDescription(props: React.ComponentProps<"p">)
function FormFieldError(props: React.ComponentProps<"p">)Source code +
"use client"
import * as React from "react"
import { Label } from "@/components/ui/label"
import { cn } from "cn"
type FormFieldContextValue = {
controlId: string
descriptionId: string
errorId: string
invalid: boolean
}
const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)
function useFormField() {
const context = React.useContext(FormFieldContext)
if (!context) throw new Error("Form field parts must be used inside FormField.")
return context
}
function FormField({
className,
id,
invalid = false,
children,
...props
}: React.ComponentProps<"div"> & { id?: string; invalid?: boolean }) {
const generatedId = React.useId()
const controlId = id ?? `field-${generatedId}`
const context = React.useMemo(
() => ({
controlId,
descriptionId: `${controlId}-description`,
errorId: `${controlId}-error`,
invalid,
}),
[controlId, invalid]
)
return (
<FormFieldContext.Provider value={context}>
<div data-slot="form-field" className={cn("grid gap-2", className)} {...props}>
{children}
</div>
</FormFieldContext.Provider>
)
}
function FormFieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
const { controlId } = useFormField()
return <Label htmlFor={controlId} className={className} {...props} />
}
function FormFieldControl({ children }: { children: React.ReactElement }) {
const { controlId, descriptionId, errorId, invalid } = useFormField()
const describedBy = `${descriptionId} ${errorId}`
return React.cloneElement(children, {
id: controlId,
"aria-describedby": describedBy,
"aria-invalid": invalid || undefined,
} as React.HTMLAttributes<HTMLElement>)
}
function FormFieldDescription({ className, ...props }: React.ComponentProps<"p">) {
const { descriptionId } = useFormField()
return (
<p
id={descriptionId}
data-slot="form-field-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function FormFieldError({ className, children, ...props }: React.ComponentProps<"p">) {
const { errorId } = useFormField()
if (!children) return null
return (
<p
id={errorId}
role="alert"
data-slot="form-field-error"
className={cn("text-sm text-destructive", className)}
{...props}
>
{children}
</p>
)
}
export {
FormField,
FormFieldControl,
FormFieldDescription,
FormFieldError,
FormFieldLabel,
useFormField,
}