Field
A structured form field wrapper with label, description, and error message support. Provides consistent layout, error display, and orientation options (vertical, horizontal, responsive). Built with 10 composable sub-components.
Anatomy
A <Field> with role="group" wraps a <FieldLabel> (or <FieldTitle>), the form control, an optional <FieldDescription>, and an optional <FieldError>. The field manages layout orientation and passes disabled state to child labels.
This is your public display name.
Sub-components
| Component | Purpose |
|---|---|
FieldSet | Wraps a group of related fields (renders <fieldset>) |
FieldLegend | Legend for a fieldset, variant: "legend" | "label" |
FieldGroup | Groups fields with consistent spacing |
Field | Individual field wrapper, orientation: vertical | horizontal | responsive |
FieldContent | Content wrapper within a field |
FieldLabel | Label rendered as a <label> element |
FieldTitle | Title text (not a <label>) |
FieldDescription | Helper text below the input |
FieldSeparator | Visual divider with optional text label |
FieldError | Error message with role="alert", supports error arrays |
FieldSuccess | Success message with role="status", supports message arrays |
Orientations
The Field component supports three layout orientations via the orientation prop.
Vertical (default)
Enter your legal name.
Horizontal
Validation States
Fields expose two validation states via data attributes. data-invalid="true" turns the field destructive red; data-valid="true" turns it success green. <FieldError> uses role="alert" for urgent announcements; <FieldSuccess> uses role="status" for polite confirmations.
Error state
We'll never share your email.
Valid state
We'll never share your email.
Multiple errors (deduplicated)
- Must be at least 8 characters
- Must include a number
Design Guidelines
Do
- Use Field for consistent form layout. It standardizes spacing, label placement, and error display across all form controls.
- Always provide FieldDescription for complex inputs. Helper text reduces user confusion and support requests.
- Show errors inline. FieldError with role="alert" ensures screen readers announce errors immediately.
- Use responsive orientation for wide forms. The "responsive" variant uses container queries to switch from vertical to horizontal.
Don't
- Don't skip the label. Even with placeholders, every field needs a FieldLabel for accessibility.
- Don't show errors before interaction. Validate on blur or submit, not on mount.
- Don't mix orientations without FieldSet. Group related fields with the same orientation in a FieldSet.
Developer Reference
Accessibility
- Field uses
role="group"to communicate field structure. - FieldError uses
role="alert"for live error announcements. - FieldSuccess uses
role="status"for polite confirmation announcements. data-invalid="true"on Field applies destructive color to all children;data-valid="true"applies success color.- FieldSet renders a native
<fieldset>with<legend>. - Uses container queries (
@md) for responsive orientation. - Error arrays are automatically deduplicated by message text.
- Disabled state propagates to child labels via
data-disabled.
Usage
import {
Field, FieldLabel, FieldDescription,
FieldError, FieldSuccess,
FieldSet, FieldLegend, FieldGroup, FieldContent
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
// Basic field
<Field>
<FieldLabel>Username</FieldLabel>
<Input placeholder="Enter username" />
<FieldDescription>This is your public display name.</FieldDescription>
</Field>
// Error state
<Field data-invalid={true}>
<FieldLabel>Email</FieldLabel>
<Input placeholder="Email" aria-invalid />
<FieldError>Please enter a valid email address.</FieldError>
</Field>
// Valid state
<Field data-valid={true}>
<FieldLabel>Email</FieldLabel>
<Input placeholder="Email" defaultValue="hello@example.com" />
<FieldSuccess>Looks good!</FieldSuccess>
</Field>
// With error array (auto-deduplicated)
<Field data-invalid={true}>
<FieldLabel>Password</FieldLabel>
<Input type="password" aria-invalid />
<FieldError errors={[
{ message: "Must be at least 8 characters" },
{ message: "Must include a number" },
]} />
</Field>
// Horizontal layout
<Field orientation="horizontal">
<FieldLabel>Name</FieldLabel>
<FieldContent>
<Input placeholder="John Doe" />
<FieldDescription>Your full legal name.</FieldDescription>
</FieldContent>
</Field>
// Fieldset with legend
<FieldSet>
<FieldLegend>Personal Information</FieldLegend>
<FieldGroup>
<Field>
<FieldLabel>First Name</FieldLabel>
<Input placeholder="John" />
</Field>
<Field>
<FieldLabel>Last Name</FieldLabel>
<Input placeholder="Doe" />
</Field>
</FieldGroup>
</FieldSet>