Field
Combine labels, controls, and help text to compose accessible form fields and grouped inputs.
Field is a Solid composition helper that uses Kobalte Label for accessible control labeling. Use the Field family to arrange labels, controls, descriptions, validation messages, and related controls.
import { Button } from "~/components/ui/button";import { Checkbox } from "~/components/ui/checkbox";import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet,} from "~/components/ui/field";import { Input } from "~/components/ui/input";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "~/components/ui/select";import { Textarea } from "~/components/ui/textarea";
const months = [ { label: "01", value: "01" }, { label: "02", value: "02" }, { label: "03", value: "03" }, { label: "04", value: "04" }, { label: "05", value: "05" }, { label: "06", value: "06" }, { label: "07", value: "07" }, { label: "08", value: "08" }, { label: "09", value: "09" }, { label: "10", value: "10" }, { label: "11", value: "11" }, { label: "12", value: "12" },];
const years = [ { label: "2024", value: "2024" }, { label: "2025", value: "2025" }, { label: "2026", value: "2026" }, { label: "2027", value: "2027" }, { label: "2028", value: "2028" }, { label: "2029", value: "2029" },];
export default function FieldDemo() { return ( <div class="w-full max-w-md"> <form> <FieldGroup> <FieldSet> <FieldLegend>Payment Method</FieldLegend> <FieldDescription>All transactions are secure and encrypted</FieldDescription> <FieldGroup> <Field> <FieldLabel for="checkout-card-name">Name on Card</FieldLabel> <Input id="checkout-card-name" placeholder="Evil Rabbit" required /> </Field> <Field> <FieldLabel for="checkout-card-number">Card Number</FieldLabel> <Input id="checkout-card-number" placeholder="1234 5678 9012 3456" required /> <FieldDescription>Enter your 16-digit card number</FieldDescription> </Field> <div class="grid grid-cols-3 gap-4"> <Field> <FieldLabel for="checkout-exp-month">Month</FieldLabel> <Select options={months} optionValue="value" optionTextValue="label" placeholder="MM" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="checkout-exp-month"> <SelectValue<(typeof months)[number]>> {(state) => state.selectedOption().label} </SelectValue> </SelectTrigger> <SelectContent /> </Select> </Field> <Field> <FieldLabel for="checkout-exp-year">Year</FieldLabel> <Select options={years} optionValue="value" optionTextValue="label" placeholder="YYYY" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="checkout-exp-year"> <SelectValue<(typeof years)[number]>> {(state) => state.selectedOption().label} </SelectValue> </SelectTrigger> <SelectContent /> </Select> </Field> <Field> <FieldLabel for="checkout-cvv">CVV</FieldLabel> <Input id="checkout-cvv" placeholder="123" required /> </Field> </div> </FieldGroup> </FieldSet> <FieldSeparator /> <FieldSet> <FieldLegend>Billing Address</FieldLegend> <FieldDescription> The billing address associated with your payment method </FieldDescription> <FieldGroup> <Field orientation="horizontal"> <Checkbox id="checkout-same-as-shipping" defaultChecked /> <FieldLabel for="checkout-same-as-shipping" class="font-normal"> Same as shipping address </FieldLabel> </Field> </FieldGroup> </FieldSet> <FieldSet> <FieldGroup> <Field> <FieldLabel for="checkout-comments">Comments</FieldLabel> <Textarea id="checkout-comments" placeholder="Add any additional comments" class="resize-none" /> </Field> </FieldGroup> </FieldSet> <Field orientation="horizontal"> <Button type="submit">Submit</Button> <Button variant="outline" type="button"> Cancel </Button> </Field> </FieldGroup> </form> </div> );}Installation
Usage
import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle,} from "~/components/ui/field";<FieldSet> <FieldLegend>Profile</FieldLegend> <FieldDescription>This appears on invoices and emails.</FieldDescription> <FieldGroup> <Field> <FieldLabel for="name">Full name</FieldLabel> <Input id="name" autocomplete="off" placeholder="Evil Rabbit" /> <FieldDescription>This appears on invoices and emails.</FieldDescription> </Field> <Field> <FieldLabel for="username">Username</FieldLabel> <Input id="username" autocomplete="off" aria-invalid /> <FieldError>Choose another username.</FieldError> </Field> <Field orientation="horizontal"> <Switch id="newsletter" /> <FieldLabel for="newsletter">Subscribe to the newsletter</FieldLabel> </Field> </FieldGroup></FieldSet>Composition
Field
A single control with label, helper text, and validation.
Field├── FieldLabel├── Input / Textarea / Switch / Select├── FieldDescription└── FieldErrorFieldGroup
Related fields in one group. Use FieldSeparator between sections when needed.
FieldGroup├── Field│ ├── FieldLabel│ ├── Input / Textarea / Switch / Select│ ├── FieldDescription│ └── FieldError├── FieldSeparator└── Field ├── FieldLabel └── Input / Textarea / Switch / SelectFieldSet
Semantic grouping with a legend and description, usually containing a FieldGroup.
FieldSet├── FieldLegend├── FieldDescription└── FieldGroup ├── Field │ ├── FieldLabel │ ├── Input / Textarea / Switch / Select │ ├── FieldDescription │ └── FieldError └── Field ├── FieldLabel └── Input / Textarea / Switch / SelectAnatomy
The Field family is designed for composing accessible forms. A typical field is structured as follows:
<Field> <FieldLabel for="input-id">Label</FieldLabel> {/* Input, Select, Switch, etc. */} <FieldDescription>Optional helper text.</FieldDescription> <FieldError>Validation message.</FieldError></Field>Fieldis the core wrapper for a single field.FieldContentis a flex column that groups label and description. It is not required without a description.- Wrap related fields with
FieldGroup, and useFieldSetwithFieldLegendfor semantic grouping.
Input
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSet,} from "~/components/ui/field";import { Input } from "~/components/ui/input";
export default function FieldInput() { return ( <FieldSet class="w-full max-w-xs"> <FieldGroup> <Field> <FieldLabel for="field-username">Username</FieldLabel> <Input id="field-username" type="text" placeholder="Max Leiter" /> <FieldDescription>Choose a unique username for your account.</FieldDescription> </Field> <Field> <FieldLabel for="field-password">Password</FieldLabel> <FieldDescription>Must be at least 8 characters long.</FieldDescription> <Input id="field-password" type="password" placeholder="••••••••" /> </Field> </FieldGroup> </FieldSet> );}Textarea
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSet,} from "~/components/ui/field";import { Textarea } from "~/components/ui/textarea";
export default function FieldTextarea() { return ( <FieldSet class="w-full max-w-xs"> <FieldGroup> <Field> <FieldLabel for="field-feedback">Feedback</FieldLabel> <Textarea id="field-feedback" placeholder="Your feedback helps us improve..." rows={4} /> <FieldDescription>Share your thoughts about our service.</FieldDescription> </Field> </FieldGroup> </FieldSet> );}Select
Select your department or area of work.
import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "~/components/ui/select";
const items = [ { label: "Engineering", value: "engineering" }, { label: "Design", value: "design" }, { label: "Marketing", value: "marketing" }, { label: "Sales", value: "sales" }, { label: "Customer Support", value: "support" }, { label: "Human Resources", value: "hr" }, { label: "Finance", value: "finance" }, { label: "Operations", value: "operations" },];
export default function FieldSelect() { return ( <Field class="w-full max-w-xs"> <FieldLabel for="field-department">Department</FieldLabel> <Select options={items} optionValue="value" optionTextValue="label" placeholder="Choose department" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="field-department"> <SelectValue<(typeof items)[number]>> {(state) => state.selectedOption().label} </SelectValue> </SelectTrigger> <SelectContent /> </Select> <FieldDescription>Select your department or area of work.</FieldDescription> </Field> );}Slider
Set your budget range ($200 - 800).
import { createSignal } from "solid-js";
import { Field, FieldDescription, FieldTitle } from "~/components/ui/field";import { Slider } from "~/components/ui/slider";
export default function FieldSlider() { const [value, setValue] = createSignal([200, 800]);
return ( <Field class="w-full max-w-xs"> <FieldTitle>Price Range</FieldTitle> <FieldDescription> Set your budget range ($<span class="font-medium tabular-nums">{value()[0]}</span> -{" "} <span class="font-medium tabular-nums">{value()[1]}</span>). </FieldDescription> <Slider value={value()} onChange={(nextValue) => setValue(nextValue)} maxValue={1000} minValue={0} step={10} class="mt-2 w-full" aria-label="Price Range" /> </Field> );}Fieldset
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet,} from "~/components/ui/field";import { Input } from "~/components/ui/input";
export default function FieldFieldset() { return ( <FieldSet class="w-full max-w-sm"> <FieldLegend>Address Information</FieldLegend> <FieldDescription>We need your address to deliver your order.</FieldDescription> <FieldGroup> <Field> <FieldLabel for="field-street">Street Address</FieldLabel> <Input id="field-street" type="text" placeholder="123 Main St" /> </Field> <div class="grid grid-cols-2 gap-4"> <Field> <FieldLabel for="field-city">City</FieldLabel> <Input id="field-city" type="text" placeholder="New York" /> </Field> <Field> <FieldLabel for="field-postal-code">Postal Code</FieldLabel> <Input id="field-postal-code" type="text" placeholder="90502" /> </Field> </div> </FieldGroup> </FieldSet> );}Checkbox
Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.
import { Checkbox } from "~/components/ui/checkbox";import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet,} from "~/components/ui/field";
export default function FieldCheckbox() { return ( <FieldGroup class="w-full max-w-xs"> <FieldSet> <FieldLegend variant="label">Show these items on the desktop</FieldLegend> <FieldDescription>Select the items you want to show on the desktop.</FieldDescription> <FieldGroup class="gap-3"> <Field orientation="horizontal"> <Checkbox id="field-hard-disks" defaultChecked /> <FieldLabel for="field-hard-disks" class="font-normal"> Hard disks </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="field-external-disks" /> <FieldLabel for="field-external-disks" class="font-normal"> External disks </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="field-cds-dvds" /> <FieldLabel for="field-cds-dvds" class="font-normal"> CDs, DVDs, and iPods </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="field-connected-servers" /> <FieldLabel for="field-connected-servers" class="font-normal"> Connected servers </FieldLabel> </Field> </FieldGroup> </FieldSet> <FieldSeparator /> <Field orientation="horizontal"> <Checkbox id="field-sync-folders" defaultChecked /> <FieldContent> <FieldLabel for="field-sync-folders">Sync Desktop & Documents folders</FieldLabel> <FieldDescription> Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices. </FieldDescription> </FieldContent> </Field> </FieldGroup> );}Radio
import { Field, FieldDescription, FieldLabel, FieldLegend, FieldSet,} from "~/components/ui/field";import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
export default function FieldRadio() { return ( <FieldSet class="w-full max-w-xs"> <FieldLegend variant="label">Subscription Plan</FieldLegend> <FieldDescription>Yearly and lifetime plans offer significant savings.</FieldDescription> <RadioGroup defaultValue="monthly"> <Field orientation="horizontal"> <RadioGroupItem value="monthly" id="field-plan-monthly" /> <FieldLabel for="field-plan-monthly" class="font-normal"> Monthly ($9.99/month) </FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="yearly" id="field-plan-yearly" /> <FieldLabel for="field-plan-yearly" class="font-normal"> Yearly ($99.99/year) </FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="lifetime" id="field-plan-lifetime" /> <FieldLabel for="field-plan-lifetime" class="font-normal"> Lifetime ($299.99) </FieldLabel> </Field> </RadioGroup> </FieldSet> );}Switch
import { Field, FieldLabel } from "~/components/ui/field";import { Switch } from "~/components/ui/switch";
export default function FieldSwitch() { return ( <Field orientation="horizontal" class="w-fit"> <FieldLabel for="field-two-factor">Multi-factor authentication</FieldLabel> <Switch id="field-two-factor" /> </Field> );}Choice Card
Wrap Field components inside FieldLabel to create selectable field groups. This works with RadioGroupItem, Checkbox, and Switch components.
import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet, FieldTitle,} from "~/components/ui/field";import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
export default function FieldChoiceCard() { return ( <FieldGroup class="w-full max-w-xs"> <FieldSet> <FieldLegend variant="label">Compute Environment</FieldLegend> <FieldDescription>Select the compute environment for your cluster.</FieldDescription> <RadioGroup defaultValue="kubernetes"> <FieldLabel for="field-kubernetes"> <Field orientation="horizontal"> <FieldContent> <FieldTitle>Kubernetes</FieldTitle> <FieldDescription>Run GPU workloads on a K8s cluster.</FieldDescription> </FieldContent> <RadioGroupItem value="kubernetes" id="field-kubernetes" /> </Field> </FieldLabel> <FieldLabel for="field-virtual-machine"> <Field orientation="horizontal"> <FieldContent> <FieldTitle>Virtual Machine</FieldTitle> <FieldDescription>Access a cluster to run GPU workloads.</FieldDescription> </FieldContent> <RadioGroupItem value="vm" id="field-virtual-machine" /> </Field> </FieldLabel> </RadioGroup> </FieldSet> </FieldGroup> );}Field Group
Stack Field components with FieldGroup. Add FieldSeparator to divide them.
import { Checkbox } from "~/components/ui/checkbox";import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSeparator, FieldSet,} from "~/components/ui/field";
export default function FieldGroupDemo() { return ( <FieldGroup class="w-full max-w-xs"> <FieldSet> <FieldLabel>Responses</FieldLabel> <FieldDescription> Get notified when ChatGPT responds to requests that take time, like research or image generation. </FieldDescription> <FieldGroup data-slot="checkbox-group"> <Field orientation="horizontal"> <Checkbox id="field-push-notifications" defaultChecked disabled /> <FieldLabel for="field-push-notifications" class="font-normal"> Push notifications </FieldLabel> </Field> </FieldGroup> </FieldSet> <FieldSeparator /> <FieldSet> <FieldLabel>Tasks</FieldLabel> <FieldDescription> Get notified when tasks you've created have updates.{" "} <a href="#field-group">Manage tasks</a> </FieldDescription> <FieldGroup data-slot="checkbox-group"> <Field orientation="horizontal"> <Checkbox id="field-task-push-notifications" /> <FieldLabel for="field-task-push-notifications" class="font-normal"> Push notifications </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="field-task-email-notifications" /> <FieldLabel for="field-task-email-notifications" class="font-normal"> Email notifications </FieldLabel> </Field> </FieldGroup> </FieldSet> </FieldGroup> );}Responsive Layout
- Vertical fields: The default orientation stacks label, control, and helper text for mobile-first layouts.
- Horizontal fields: Set
orientation="horizontal"onFieldto align the label and control side-by-side. Pair withFieldContentto keep descriptions aligned. - Responsive fields: Set
orientation="responsive"for automatic column layouts inside container-aware parents.FieldGroupprovides the@container/field-groupcontainer used by the component styles.
import { Button } from "~/components/ui/button";import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet,} from "~/components/ui/field";import { Input } from "~/components/ui/input";
export default function FieldResponsive() { return ( <div class="w-full max-w-lg"> <form> <FieldSet> <FieldLegend>Profile</FieldLegend> <FieldDescription>Fill in your profile information.</FieldDescription> <FieldGroup> <Field orientation="responsive"> <FieldContent> <FieldLabel for="field-responsive-name">Name</FieldLabel> <FieldDescription>Provide your full name for identification</FieldDescription> </FieldContent> <Input id="field-responsive-name" placeholder="Evil Rabbit" required /> </Field> <Field orientation="responsive"> <Button type="submit">Submit</Button> <Button type="button" variant="outline"> Cancel </Button> </Field> </FieldGroup> </FieldSet> </form> </div> );}Validation and Errors
- Add
data-invalidtoFieldto switch the entire block into an error state. - Add
aria-invalidon the input itself for assistive technologies. - Render
FieldErrorimmediately after the control or insideFieldContentto keep error messages aligned with the field.
<Field data-invalid> <FieldLabel for="email">Email</FieldLabel> <Input id="email" type="email" aria-invalid /> <FieldError>Enter a valid email address.</FieldError></Field>Accessibility
FieldSetandFieldLegendkeep related controls grouped for keyboard and assistive-tech users.Fieldoutputsrole="group"so nested controls inherit labeling fromFieldLabelandFieldLegendwhen combined.- Apply
FieldSeparatorsparingly so screen readers encounter clear section boundaries.
API Reference
For primitive label props and keyboard behavior, see the Kobalte Label API reference.
FieldSet
Container that renders a semantic fieldset with spacing presets.
<FieldSet> <FieldLegend>Delivery</FieldLegend> <FieldGroup>{/* Fields */}</FieldGroup></FieldSet>FieldLegend
Legend element for a FieldSet. Switch to the label variant to align with label sizing.
<FieldLegend variant="label">Notification Preferences</FieldLegend>The FieldLegend has two variants: legend and label. The label variant applies label sizing and alignment. Handy if you have a nested FieldSet.
FieldGroup
Layout wrapper that stacks Field components and enables container queries for responsive orientations.
<FieldGroup class="@container/field-group flex flex-col gap-6"> <Field>{/* ... */}</Field> <Field>{/* ... */}</Field></FieldGroup>Field
The core wrapper for a single field. Provides orientation control, invalid state styling, and spacing.
<Field orientation="horizontal"> <FieldLabel for="remember">Remember me</FieldLabel> <Switch id="remember" /></Field>FieldContent
Flex column that groups control and descriptions when the label sits beside the control. Not required if you have no description.
<Field> <Checkbox id="notifications" /> <FieldContent> <FieldLabel for="notifications">Notifications</FieldLabel> <FieldDescription>Email, SMS, and push options.</FieldDescription> </FieldContent></Field>FieldLabel
Label styled for both direct inputs and nested Field children.
<FieldLabel for="email">Email</FieldLabel>FieldTitle
Renders a title with label styling inside FieldContent.
<FieldContent> <FieldTitle>Enable Touch ID</FieldTitle> <FieldDescription>Unlock your device faster.</FieldDescription></FieldContent>FieldDescription
Helper text slot that automatically balances long lines in horizontal layouts.
<FieldDescription>We never share your email with anyone.</FieldDescription>FieldSeparator
Visual divider to separate sections inside a FieldGroup. Accepts optional inline content.
<FieldSeparator>Or continue with</FieldSeparator>FieldError
Accessible error container that accepts children or an errors array (e.g., from a form library).
<FieldError errors={errors.username} />When the errors array contains multiple messages, the component renders a list automatically.