Skip to main content

Field

Field is the wrapper you put around a form control. It renders the label, helper text, and validation message, wires them to the control with the right id / aria-describedby / aria-invalid associations, and cascades disabled, required, invalid, optional, and readOnly down to whatever control sits inside it.

You set state once on the Field; the control inherits it.


function PlaygroundDemo() {
  return (
    <div className="grid w-full max-w-120 gap-5">
      <Field required>
        <Field.Label>Passenger name</Field.Label>
        <Input>
          <Input.Field placeholder="Ada Lovelace" />
        </Input>
        <Field.Description>Exactly as printed on your passport.</Field.Description>
      </Field>

      <Field invalid>
        <Field.Label>Email</Field.Label>
        <Input>
          <Input.Field defaultValue="ada@" />
        </Input>
        <Field.ErrorMessage>Enter a complete email address.</Field.ErrorMessage>
      </Field>

      <Field disabled>
        <Field.Label>Loyalty number</Field.Label>
        <Input>
          <Input.Field defaultValue="TK-4471" />
        </Input>
        <Field.Description>Locked while your booking is being processed.</Field.Description>
      </Field>
    </div>
  );
}

Anatomy

Compose only the parts you need — each is optional except the control itself:

<Field>
<Field.Label />
{/* your control: Input, Switch, Checkbox, Radio, Select, Slider, … */}
<Field.Description />
<Field.ErrorMessage />
</Field>

Field.ErrorMessage renders only while the field is invalid, so you can leave it mounted unconditionally.

Validation state

Drive invalid from your own validation and swap the description for the error message. The control picks up aria-invalid from the Field — you don't set it on the Input.


function StateDemo() {
  const [email, setEmail] = React.useState('ada@');
  const invalid = !email.includes('@') || email.endsWith('@');

  return (
    <Field invalid={invalid} required className="w-full max-w-100">
      <Field.Label>Email</Field.Label>
      <Input>
        <Input.Field
          value={email}
          onChange={event => setEmail(event.target.value)}
          placeholder="ada@example.com"
        />
      </Input>
      {invalid ? (
        <Field.ErrorMessage>Enter a complete email address.</Field.ErrorMessage>
      ) : (
        <Field.Description>We only use this for booking updates.</Field.Description>
      )}
    </Field>
  );
}

Any control, not just Input

The same wrapper works for Switch, Checkbox, Radio, Select, and Slider. Nothing about the markup changes.


function NonInputDemo() {
  return (
    <Field className="w-full max-w-100">
      <Field.Label>Seat alerts</Field.Label>
      <Switch defaultChecked />
      <Field.Description>Notify me when a window seat frees up.</Field.Description>
    </Field>
  );
}

tip

Reach for Label instead when you only need the label primitive — a section title, or a manual htmlFor association to a native control. Field is for the full label + description + error layout with shared state.

Accessibility

  • The label is associated with the control via a generated id, so Field.Label needs no htmlFor.
  • Field.Description and Field.ErrorMessage are linked through aria-describedby on the control.
  • Field.ErrorMessage carries role="alert", so a validation failure is announced when it appears.
  • required renders the asterisk as a decorative slot; the requirement itself is conveyed to assistive technology through the control's required attribute, not the glyph.
  • Setting disabled or readOnly on the Field applies the matching state to the nested control — don't set it in both places.

API

Field

Props

NameTypeDefaultDescription
childrenReact.ReactNode-Field parts and the control they describe.
classNamesPartial<Record<"root", string>>-Per-slot extra classes.
slotPropsPartial<Record<"root", React.HTMLAttributes<HTMLElement>>>-Per-slot HTML-attribute overrides.
idstring-Base id for the generated label / description / error associations. Generated when omitted.
disabledbooleanfalseDisables the field and every control inside it.
requiredbooleanfalseMarks the field required. Renders the asterisk slot on Field.Label.
readOnlybooleanfalseMarks the field read-only and cascades to the nested control.
invalidbooleanfalseMarks the field invalid. Reveals Field.ErrorMessage and cascades to the nested control.
optionalbooleanfalseMarks the field optional. Mutually exclusive with required in practice.
classNamestring-Appends custom classes to the root slot.

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-invalidinvalid is true.Styling hook for the invalid state.
data-disableddisabled is true.Styling hook for the disabled state.
data-requiredrequired is true.Styling hook for required fields.
data-optionaloptional is true.Styling hook for optional fields.
data-readonlyreadOnly is true.Styling hook for read-only fields.

Field.Label

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for the label element.
data-slot="asterisk"The field is required.Stable selector for the required marker.

Field.Description

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for the description element.
data-slot="icon"An icon is rendered inside the description.Stable selector for the description icon.

Field.ErrorMessage

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for the error message element.
data-slot="icon"An icon is rendered inside the error message.Stable selector for the error icon.

Type Definitions

NameDefinition
FieldLabelSlot'root' | 'asterisk'
FieldDescriptionSlot'root' | 'icon'
FieldErrorMessageSlot'root' | 'icon'