Light

Field

A form field wrapper that composes a label, input, description, and error message.

Spec · from metadata

When to use

  • Wrapping any form control (Input, Select, Textarea, etc.) with a label
  • Displaying field-level validation errors
  • Building form layouts with consistent spacing and alignment
  • Grouping related fields with FieldSet, FieldGroup, and FieldLegend

When not to use

  • Standalone labels without a form control — use Label directly
  • Page-level error messages — use Alert or toast instead
  • Non-form layout — use standard flex/grid utilities

Variants

PropValuesDefaultDescription
orientationverticalhorizontalresponsiveverticalvertical stacks label above input. horizontal places them side by side. responsive switches at @md container breakpoint.
variant (FieldLegend)legendlabellegendControls font size: legend is text-base, label is text-sm.

Anti-patterns

Avoid<label>Email</label>
<Input id="email" />
Prefer<Field>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" />
</Field>

Use Field + FieldLabel for consistent spacing, error handling, and styling.

Avoid<Field orientation="horizontal">
  <FieldLabel>Name</FieldLabel>
  <Input />
  <FieldDescription>Your full name</FieldDescription>
  <FieldError>Required</FieldError>
</Field>
Prefer<Field orientation="horizontal">
  <FieldLabel>Name</FieldLabel>
  <FieldContent>
    <Input />
    <FieldDescription>Your full name</FieldDescription>
    <FieldError>Required</FieldError>
  </FieldContent>
</Field>

In horizontal orientation, wrap input + description + error in FieldContent for proper layout.

Accessibility

  • Required ARIAdata-invalid on Field for error styling
  • Screen readerFieldError renders with role='alert' for live region announcements. FieldSet uses semantic <fieldset> with <legend>.
  • ContrastError text uses text-destructive. Description uses text-muted-foreground.

Token bindings

TokenCategoryUsage
destructivecolorError text and invalid state styling
muted-foregroundcolorDescription text
primarycolorChecked state highlight on FieldLabel with checkbox/radio
bordercolorFieldSeparator line

Import

import {
  Field,
  FieldContent,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSeparator,
  FieldSet,
  FieldTitle,
} from "@timelycare/helix-ui"

Props

interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {
  orientation?: "vertical" | "horizontal" | "responsive"
  className?: string
  children: React.ReactNode
}

interface FieldLabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
  htmlFor?: string
  className?: string
  children: React.ReactNode
}

interface FieldDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
  className?: string
  children: React.ReactNode
}

interface FieldErrorProps extends React.HTMLAttributes<HTMLParagraphElement> {
  className?: string
  children: React.ReactNode
}

interface FieldGroupProps extends React.HTMLAttributes<HTMLDivElement> {
  className?: string
  children: React.ReactNode
}

interface FieldSetProps extends React.HTMLAttributes<HTMLFieldSetElement> {
  className?: string
  children: React.ReactNode
}

interface FieldLegendProps extends React.HTMLAttributes<HTMLLegendElement> {
  variant?: "default" | "label"
  className?: string
  children: React.ReactNode
}

interface FieldContentProps extends React.HTMLAttributes<HTMLDivElement> {
  className?: string
  children: React.ReactNode
}

interface FieldTitleProps extends React.HTMLAttributes<HTMLParagraphElement> {
  className?: string
  children: React.ReactNode
}

interface FieldSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
  className?: string
}

Styling

Typography

ElementFontSizeWeightColor
LabelAdelle Sans Medium14px (text-sm)font-mediumtext-foreground
LegendAdelle Sans Semibold16px (text-base)font-semiboldtext-foreground
Legend (label variant)Adelle Sans Medium14px (text-sm)font-mediumtext-foreground
TitleAdelle Sans Medium14px (text-sm)font-mediumtext-foreground
DescriptionAdelle Sans Regular14px (text-sm)font-normaltext-muted-foreground
ErrorAdelle Sans Regular14px (text-sm)font-normaltext-destructive

Spacing

PropertyValueTailwind
Field gap (vertical)8pxgap-2
FieldGroup gap16pxgap-4
FieldSeparator1px borderborder-t border-border
Label to input8pxHandled by Field's gap-2

Orientation

OrientationBehavior
vertical (default)Label stacks above input
horizontalLabel and input sit side-by-side
responsiveVertical on mobile, horizontal on desktop (container-based)

Sub-Components

ComponentPurpose
FieldIndividual field wrapper (label + control + description/error)
FieldLabelForm label with htmlFor association
FieldDescriptionHelper text below or near the control
FieldErrorValidation error message in text-destructive. When passed an errors array, duplicate messages are automatically deduplicated before rendering.
FieldGroupGroups multiple fields with consistent spacing
FieldSetSemantic <fieldset> wrapper for related fields
FieldLegend<legend> for a FieldSet with heading styling
FieldContentWrapper for label + description when using horizontal/responsive layout
FieldTitleTitle text within a selectable field (e.g., radio card)
FieldSeparatorVisual divider between field sections

Common Patterns

Basic Input Field

<Field>
  <FieldLabel htmlFor="username">Username</FieldLabel>
  <Input id="username" placeholder="Enter username" />
  <FieldDescription>Choose a unique username for your account.</FieldDescription>
</Field>

With Validation Error

<Field>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input
    id="email"
    placeholder="m@example.com"
    className="border-destructive focus-visible:ring-destructive/50"
  />
  <FieldError>Please enter a valid email address.</FieldError>
</Field>

Grouped Fields with FieldSet

<FieldSet>
  <FieldLegend>Address Information</FieldLegend>
  <FieldDescription>We need your address to deliver your order.</FieldDescription>
  <FieldGroup>
    <Field>
      <FieldLabel htmlFor="street">Street Address</FieldLabel>
      <Input id="street" placeholder="123 Main St" />
    </Field>
    <div className="grid grid-cols-2 gap-4">
      <Field>
        <FieldLabel htmlFor="city">City</FieldLabel>
        <Input id="city" placeholder="New York" />
      </Field>
      <Field>
        <FieldLabel htmlFor="zip">Postal Code</FieldLabel>
        <Input id="zip" placeholder="90502" />
      </Field>
    </div>
  </FieldGroup>
</FieldSet>

Horizontal Checkbox

<Field orientation="horizontal">
  <Checkbox id="terms" />
  <FieldLabel htmlFor="terms" className="font-normal">
    I agree to the terms and conditions
  </FieldLabel>
</Field>

Checkbox Group with FieldSet

<FieldSet>
  <FieldLegend variant="label">Notification Preferences</FieldLegend>
  <FieldDescription>Select how you'd like to be notified.</FieldDescription>
  <FieldGroup className="gap-3">
    <Field orientation="horizontal">
      <Checkbox id="email-notif" />
      <FieldLabel htmlFor="email-notif" className="font-normal">Email</FieldLabel>
    </Field>
    <Field orientation="horizontal">
      <Checkbox id="sms-notif" />
      <FieldLabel htmlFor="sms-notif" className="font-normal">SMS</FieldLabel>
    </Field>
  </FieldGroup>
</FieldSet>

Responsive Layout

<FieldSet>
  <FieldLegend>Profile</FieldLegend>
  <FieldDescription>Fill in your profile information.</FieldDescription>
  <FieldSeparator />
  <FieldGroup>
    <Field orientation="responsive">
      <FieldContent>
        <FieldLabel htmlFor="name">Name</FieldLabel>
        <FieldDescription>Provide your full name</FieldDescription>
      </FieldContent>
      <Input id="name" placeholder="Jane Doe" required />
    </Field>
    <FieldSeparator />
    <Field orientation="responsive">
      <FieldContent>
        <FieldLabel htmlFor="bio">Bio</FieldLabel>
        <FieldDescription>Keep it under 100 characters.</FieldDescription>
      </FieldContent>
      <Textarea id="bio" placeholder="Tell us about yourself" className="resize-none" />
    </Field>
  </FieldGroup>
</FieldSet>

Selectable Radio Card

<FieldSet>
  <FieldLabel htmlFor="plan">Choose a Plan</FieldLabel>
  <RadioGroup defaultValue="basic">
    <FieldLabel htmlFor="basic">
      <Field orientation="horizontal" className="rounded-lg bg-input-bg">
        <FieldContent>
          <FieldTitle>Basic</FieldTitle>
          <FieldDescription>Great for getting started.</FieldDescription>
        </FieldContent>
        <RadioGroupItem value="basic" id="basic" />
      </Field>
    </FieldLabel>
    <FieldLabel htmlFor="pro">
      <Field orientation="horizontal" className="rounded-lg bg-input-bg">
        <FieldContent>
          <FieldTitle>Pro</FieldTitle>
          <FieldDescription>For power users and teams.</FieldDescription>
        </FieldContent>
        <RadioGroupItem value="pro" id="pro" />
      </Field>
    </FieldLabel>
  </RadioGroup>
</FieldSet>

Accessibility

  • FieldLabel renders a <label> with htmlFor association to the input
  • FieldSet renders a semantic <fieldset> element
  • FieldLegend renders a <legend> for the fieldset
  • FieldDescription should be linked via aria-describedby on the input
  • FieldError should be linked via aria-describedby and the input should have aria-invalid="true"
  • Keyboard: Tab moves between fields; Space/Enter activates checkboxes and radios

Gotchas

ProblemSolution
Label not linked to inputEnsure htmlFor on FieldLabel matches the input id
Error message not announcedAdd aria-describedby pointing to the error element
Responsive layout not switchingUse orientation="responsive" — requires container query support
Checkbox label too boldAdd className="font-normal" to FieldLabel for checkbox/radio fields
Description above inputPlace FieldDescription before or after the input — order in JSX determines visual order
Multi-column fields misalignedWrap in a div with grid grid-cols-2 gap-4 inside FieldGroup

See Also


Last updated: February 24, 2026