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
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 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.
<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>
);
}