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

ComponentPurpose
FieldSetWraps a group of related fields (renders <fieldset>)
FieldLegendLegend for a fieldset, variant: "legend" | "label"
FieldGroupGroups fields with consistent spacing
FieldIndividual field wrapper, orientation: vertical | horizontal | responsive
FieldContentContent wrapper within a field
FieldLabelLabel rendered as a <label> element
FieldTitleTitle text (not a <label>)
FieldDescriptionHelper text below the input
FieldSeparatorVisual divider with optional text label
FieldErrorError message with role="alert", supports error arrays
FieldSuccessSuccess 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.

Looks good!

Multiple errors (deduplicated)

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>