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'