Folio

Components

Field

A form field wrapper that pairs a label and a control with optional helper text or a validation message, all wired together for accessibility.

Field is a set of compound parts. Field, FieldLabel, FieldDescription, and FieldError wrap a control (Input, Select, Textarea, and the like) and do not replace it. You link the label, the control id, and the message with htmlFor, id, and aria-describedby.

Preview

We only use this for account recovery.

Rendered with live Folio tokens. Type in the field, no dark: classes.

Variants

<Field>
  <FieldLabel htmlFor="name">Full name</FieldLabel>
  <Input id="name" />
</Field>

We only use this for account recovery.

<Field>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" type="email" aria-describedby="email-help" />
  <FieldDescription id="email-help">…</FieldDescription>
</Field>
<Field data-invalid>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" type="email" aria-invalid aria-describedby="email-error" />
  <FieldError id="email-error">…</FieldError>
</Field>
<Field>
  <FieldLabel htmlFor="workspace">
    Workspace name
    <span aria-hidden className="ml-0.5 text-destructive">*</span>
  </FieldLabel>
  <Input id="workspace" required />
</Field>

24 / 160 characters

<Field>
  <FieldLabel htmlFor="bio">Bio</FieldLabel>
  <Textarea id="bio" aria-describedby="bio-count" />
  <FieldDescription id="bio-count">24 / 160 characters</FieldDescription>
</Field>

Helper text in FieldDescription, an error in FieldError, a required marker as a child of FieldLabel, and a character count in the description slot. For an error, also set aria-invalid on the control so assistive tech reads it as invalid.

States

Default. Empty and at rest.

Focused. The control rings (shown statically).

Filled. Holds a value.

Error. Danger border and message.

Disabled. Dimmed, not editable.

Focus and validation read on the control. Set data-disabled on Field and disabled on the control to dim the label and the control together.

API

Part
Key prop
Default
Description
Field
"vertical" | "horizontal" | "responsive"
"vertical"
orientation -- the wrapper for one label, control, and message. Set data-invalid or data-disabled to mark the whole field.
FieldLabel
Label props
—
The field label. Set htmlFor to the control id. Put a required asterisk in it as a child span.
FieldDescription
p props
—
Helper text below the control, for example a hint or a character count.
FieldError
{ message?: string }[]
undefined
errors -- the validation message, from children or from an errors array. Renders nothing when empty, and has role="alert".
FieldContent
div props
—
Stacks a label and a description next to a control in a horizontal field.
FieldGroup
boolean
false
compact -- stacks several fields with even spacing. compact tightens the gaps for every field inside it.
FieldSet / FieldLegend
"legend" | "label"
"legend"
A native fieldset with a legend for a named group of fields. The legend variant sets its size.
FieldTitle / FieldSeparator
div props
—
A label-styled title that is not a <label>, and a divider with optional centered text.

The parts do not wire ids for you. Give the control an id, point FieldLabel htmlFor at it, and list the description or error id in the control's aria-describedby.

Don't and Do

Don't

Don't drop a raw Input into a form without a Field and a FieldLabel around it. An input with no associated label is invisible to screen readers and gives sighted readers nothing to scan. Wrap form controls in a Field so every one carries its label and messaging.

Do
<Field>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" type="email" aria-describedby="email-help" />
  <FieldDescription id="email-help">We never share it.</FieldDescription>
</Field>

Use Field any time a control needs a label, helper text, or a validation message. It is the standard wrapper for form inputs in Folio.

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/components/shadcn/field";
import { Input } from "@/components/shadcn/input";

export function EmailField() {
  return (
    <Field>
      <FieldLabel htmlFor="email">Email</FieldLabel>
      <Input
        id="email"
        type="email"
        placeholder="you@distyl.ai"
        aria-describedby="email-help"
      />
      <FieldDescription id="email-help">
        We only use this for account recovery.
      </FieldDescription>
    </Field>
  );
}
API matches @distylai/toolkit-ui -- compound parts Field, FieldLabel, FieldDescription, FieldError, FieldContent, FieldGroup, FieldSet, FieldLegend, FieldTitle, and FieldSeparator. The classes use Folio tokens.