Field
Associates a label, control, description, and validation error for a form field.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";
export default function FieldDemo() {
return (
<div className="w-full max-w-sm">
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input required type="email" placeholder="name@example.com" />
<FieldDescription>Used to send you order updates.</FieldDescription>
</Field>
</div>
);
}
Installation
npx shadcn@latest add https://sevenui.dev/r/field.jsonUsage
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input required type="email" placeholder="name@example.com" />
<FieldDescription>Used to send you order updates.</FieldDescription>
<FieldError />
</Field>;
Field wires everything together for you: htmlFor/id between label and
control, aria-describedby for the description and error, and
aria-invalid on the control are all managed by the underlying Base UI
Field context — no manual ids. Every SevenUI form control (Input, Textarea,
Checkbox, Radio Group, Select, Switch, Slider, Number Field, …) participates
automatically.
Examples
Orientation
horizontal places the label and control on one row — pair it with
FieldContent to stack a label and description beside the control.
responsive stacks in narrow containers and switches to the horizontal
layout from the @md container breakpoint; it requires a FieldGroup
ancestor, which provides the container query scope.
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";
import { Switch } from "@/registry/base/ui/switch";
export default function FieldOrientation() {
return (
<FieldGroup className="max-w-md">
<Field orientation="horizontal" name="marketing">
<FieldContent>
<FieldLabel>Marketing emails</FieldLabel>
<FieldDescription>Occasional product news. No spam.</FieldDescription>
</FieldContent>
<Switch />
</Field>
<Field orientation="responsive" name="displayName">
<FieldContent>
<FieldLabel>Display name</FieldLabel>
<FieldDescription>
Stacks on narrow containers, inline on wide ones.
</FieldDescription>
</FieldContent>
<Input placeholder="Margaret Hamilton" />
</Field>
</FieldGroup>
);
}
Validation
A bare <FieldError /> renders the field’s active error on its own —
native constraint messages (required, type="email", …), messages
returned by validate, or server errors passed through
Form. This example validates on blur with a custom
validate function.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";
export default function FieldValidation() {
return (
<div className="w-full max-w-sm">
<Field
name="username"
validationMode="onBlur"
validate={(value) =>
String(value ?? "").length >= 3
? null
: "Username must be at least 3 characters."
}
>
<FieldLabel>Username</FieldLabel>
<Input required placeholder="sevenui" />
<FieldDescription>
Validates when the input loses focus.
</FieldDescription>
<FieldError />
</Field>
</div>
);
}
To scope a literal message to one native validity state, use match:
<FieldError match="valueMissing">Please enter your email.</FieldError>
<FieldError match="typeMismatch">Not a valid email address.</FieldError>
React Hook Form
Any form library can drive a field through three generic props — invalid,
touched, and dirty — plus the errors array on FieldError. With
react-hook-form, spread fieldState from a Controller:
"use client";
import { Controller, useForm } from "react-hook-form";
import { Button } from "@/registry/base/ui/button";
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";
type FormValues = { username: string };
export default function FieldRhf() {
const form = useForm<FormValues>({
defaultValues: { username: "" },
});
function onSubmit(values: FormValues) {
console.log(values);
}
return (
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full max-w-sm gap-4"
noValidate
>
<Controller
control={form.control}
name="username"
rules={{
required: "Username is required.",
minLength: {
value: 3,
message: "Username must be at least 3 characters.",
},
}}
render={({ field, fieldState }) => (
<Field
name={field.name}
invalid={fieldState.invalid}
touched={fieldState.isTouched}
dirty={fieldState.isDirty}
>
<FieldLabel>Username</FieldLabel>
<Input placeholder="sevenui" {...field} />
<FieldDescription>
This is your public display name.
</FieldDescription>
<FieldError
errors={fieldState.error ? [fieldState.error] : undefined}
/>
</Field>
)}
/>
<Button type="submit">Submit</Button>
</form>
);
}
The same pattern works for TanStack Form
(field.state.meta.isTouched, field.state.meta.errors) or any validator
implementing Standard Schema — the component has no form-library imports.
API reference
Field
Extends the Base UI Field Root — all its props apply.
| Prop | Type | Default |
|---|---|---|
orientation |
"vertical" | "horizontal" | "responsive" |
"vertical" |
name |
string — keys the field for form submission and Form errors |
— |
validate |
(value, formValues) => string | string[] | null | Promise<...> |
— |
validationMode |
"onSubmit" | "onBlur" | "onChange" |
"onSubmit" |
invalid / touched / dirty |
boolean — for external form libraries |
— |
disabled |
boolean — supersedes the control’s own disabled |
false |
Every part carries the field’s state as data attributes for styling:
data-invalid, data-valid, data-dirty, data-touched, data-filled,
data-focused, data-disabled.
FieldLabel
Extends the Base UI Field Label — associated with the field’s control
automatically, no htmlFor needed. Renders through
Label, so label styling applies. Wrapping a
nested Field turns it into a selectable card (checkbox/radio card
patterns).
FieldDescription
Extends the Base UI Field Description — linked to the control via
aria-describedby automatically.
FieldError
Extends the Base UI Field Error. Visible only while the field is invalid, unless external content forces it.
| Prop | Type | Default |
|---|---|---|
errors |
Array<{ message?: string } | undefined> |
— |
match |
boolean | keyof ValidityState |
— |
With no props it auto-renders the field’s active error from context.
errors accepts issues from any form library or Standard Schema validator
(Zod, Valibot, ArkType); messages are deduped, and multiple messages render
as a list. match="valueMissing" (and other
ValidityState
keys) scopes literal children to one native validity condition.
Layout parts
Plain styled elements with no form wiring — compose freely around any number of fields:
| Component | Description |
|---|---|
FieldSet |
<fieldset> grouping related fields. |
FieldLegend |
<legend>; variant: "legend" (default) or "label". |
FieldGroup |
Vertical stack of fields; container-query scope for responsive. |
FieldContent |
Column wrapper pairing a label/description beside a control. |
FieldTitle |
Label-styled heading for non-label contexts. |
FieldSeparator |
Divider between fields, with optional inline content. |