Bring labels, controls, help text, and errors together in accessible fields and groups.
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 { NativeSelect, NativeSelectOption,} from "@/components/ui/native-select"import { Textarea } from "@/components/ui/textarea"export function Example() { return ( <form className="flex w-full flex-col gap-6"> <FieldSet> <FieldLegend>Payment Method</FieldLegend> <FieldDescription> All transactions are secure and encrypted. </FieldDescription> <FieldGroup> <Field> <FieldLabel htmlFor="name-on-card">Name on Card</FieldLabel> <Input id="name-on-card" placeholder="Jane Doe" autoComplete="off" /> </Field> <Field> <FieldLabel htmlFor="card-number">Card Number</FieldLabel> <Input id="card-number" placeholder="ACCT-000035" autoComplete="off" /> <FieldDescription> Enter your 16-digit card number. </FieldDescription> </Field> <div className="grid gap-4 sm:grid-cols-3"> <Field> <FieldLabel htmlFor="month">Month</FieldLabel> <NativeSelect id="month" defaultValue=""> <NativeSelectOption value="" disabled> MM </NativeSelectOption> <NativeSelectOption value="01">01</NativeSelectOption> <NativeSelectOption value="06">06</NativeSelectOption> <NativeSelectOption value="12">12</NativeSelectOption> </NativeSelect> </Field> <Field> <FieldLabel htmlFor="year">Year</FieldLabel> <NativeSelect id="year" defaultValue=""> <NativeSelectOption value="" disabled> YYYY </NativeSelectOption> <NativeSelectOption value="2026">2026</NativeSelectOption> <NativeSelectOption value="2027">2027</NativeSelectOption> <NativeSelectOption value="2028">2028</NativeSelectOption> </NativeSelect> </Field> <Field> <FieldLabel htmlFor="cvv">CVV</FieldLabel> <Input id="cvv" placeholder="123" autoComplete="off" /> </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="same-as-shipping" defaultChecked /> <FieldLabel htmlFor="same-as-shipping" className="font-normal"> Same as shipping address </FieldLabel> </Field> <Field> <FieldLabel htmlFor="comments">Comments</FieldLabel> <Textarea id="comments" placeholder="Add any extra notes..." className="min-h-20" /> </Field> </FieldGroup> </FieldSet> <div className="flex justify-end gap-2"> <Button type="button" variant="secondary"> Cancel </Button> <Button type="button">Submit</Button> </div> </form> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add fieldUsage
import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle,} from "@/components/ui/field"<FieldSet> <FieldLegend>Payment method</FieldLegend> <FieldDescription>Card details are encrypted in transit.</FieldDescription> <FieldGroup> <Field> <FieldLabel htmlFor="name">Name on card</FieldLabel> <Input id="name" placeholder="Jordan Lee" /> </Field> </FieldGroup></FieldSet>Composition
Field
Field ├── FieldLabel ├── Input / Textarea / Switch / NativeSelect ├── FieldDescription └── FieldError
FieldGroup
FieldGroup ├── Field ├── FieldSeparator └── Field
FieldSet
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
└── FieldInput
Choose a unique username for your account.
Must be at least 8 characters long.
import { Field, FieldDescription, FieldGroup, FieldLabel,} from "@/components/ui/field"import { Input } from "@/components/ui/input"export function Example() { return ( <FieldGroup> <Field> <FieldLabel htmlFor="username">Username</FieldLabel> <Input id="username" placeholder="your-username" autoComplete="off" /> <FieldDescription> Choose a unique username for your account. </FieldDescription> </Field> <Field> <FieldLabel htmlFor="password">Password</FieldLabel> <Input id="password" type="password" placeholder="••••••••" /> <FieldDescription> Must be at least 8 characters long. </FieldDescription> </Field> </FieldGroup> )}Textarea
Share your thoughts about our service.
import { Field, FieldDescription, FieldLabel,} from "@/components/ui/field"import { Textarea } from "@/components/ui/textarea"export function Example() { return ( <Field> <FieldLabel htmlFor="feedback">Feedback</FieldLabel> <Textarea id="feedback" placeholder="Share your thoughts..." className="min-h-24" /> <FieldDescription> Share your thoughts about our service. </FieldDescription> </Field> )}Native Select
Use Native Select when the field needs a select control.
Select your department or area of work.
import { Field, FieldDescription, FieldLabel,} from "@/components/ui/field"import { NativeSelect, NativeSelectOption,} from "@/components/ui/native-select"export function Example() { return ( <Field> <FieldLabel htmlFor="department">Department</FieldLabel> <NativeSelect id="department" defaultValue="" className="w-full"> <NativeSelectOption value="" disabled> Choose department </NativeSelectOption> <NativeSelectOption value="engineering"> Engineering </NativeSelectOption> <NativeSelectOption value="design">Design</NativeSelectOption> <NativeSelectOption value="marketing">Marketing</NativeSelectOption> </NativeSelect> <FieldDescription> Select your department or area of work. </FieldDescription> </Field> )}Slider
Set your budget range ($200 - 800).
import { Field, FieldDescription, FieldLabel,} from "@/components/ui/field"import { Slider } from "@/components/ui/slider"export function Example() { return ( <Field> <FieldLabel>Price Range</FieldLabel> <Slider defaultValue={[200, 800]} min={0} max={1000} step={10} /> <FieldDescription> Set your budget range ($200 - 800). </FieldDescription> </Field> )}Fieldset
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet,} from "@/components/ui/field"import { Input } from "@/components/ui/input"export function Example() { return ( <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" autoComplete="off" /> </Field> <div className="grid gap-4 sm:grid-cols-2"> <Field> <FieldLabel htmlFor="city">City</FieldLabel> <Input id="city" placeholder="San Francisco" autoComplete="off" /> </Field> <Field> <FieldLabel htmlFor="postal">Postal Code</FieldLabel> <Input id="postal" placeholder="94103" autoComplete="off" /> </Field> </div> </FieldGroup> </FieldSet> )}Checkbox
import { Checkbox } from "@/components/ui/checkbox"import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet,} from "@/components/ui/field"export function Example() { return ( <FieldSet> <FieldLegend variant="label"> Show these items on the desktop </FieldLegend> <FieldDescription> Select the items you want to show on the desktop. </FieldDescription> <FieldGroup className="gap-3"> <Field orientation="horizontal"> <Checkbox id="hard-disks" defaultChecked /> <FieldLabel htmlFor="hard-disks" className="font-normal"> Hard disks </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="external-disks" /> <FieldLabel htmlFor="external-disks" className="font-normal"> External disks </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="cds" /> <FieldLabel htmlFor="cds" className="font-normal"> CDs, DVDs, and iPods </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="servers" /> <FieldLabel htmlFor="servers" className="font-normal"> Connected servers </FieldLabel> </Field> </FieldGroup> </FieldSet> )}Radio
import { Field, FieldDescription, FieldLabel, FieldLegend, FieldSet,} from "@/components/ui/field"import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() { return ( <FieldSet> <FieldLegend variant="label">Subscription Plan</FieldLegend> <FieldDescription> Yearly and lifetime plans offer significant savings. </FieldDescription> <RadioGroup defaultValue="yearly" className="gap-3"> <Field orientation="horizontal"> <RadioGroupItem value="monthly" id="plan-monthly" /> <FieldLabel htmlFor="plan-monthly" className="font-normal"> Monthly ($9.99/month) </FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="yearly" id="plan-yearly" /> <FieldLabel htmlFor="plan-yearly" className="font-normal"> Yearly ($99.99/year) </FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="lifetime" id="plan-lifetime" /> <FieldLabel htmlFor="plan-lifetime" className="font-normal"> Lifetime ($299.99) </FieldLabel> </Field> </RadioGroup> </FieldSet> )}Switch
import { Field, FieldLabel } from "@/components/ui/field"import { Switch } from "@/components/ui/switch"export function Example() { return ( <Field orientation="horizontal"> <FieldLabel htmlFor="mfa">Multi-factor authentication</FieldLabel> <Switch id="mfa" /> </Field> )}Choice Card
Place Field inside FieldLabel to create a selectable card. This works with radios, checkboxes, and switches.
import { Field, FieldContent, FieldDescription, FieldLabel, FieldLegend, FieldSet, FieldTitle,} from "@/components/ui/field"import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() { return ( <FieldSet> <FieldLegend variant="label">Compute Environment</FieldLegend> <FieldDescription> Select the compute environment for your cluster. </FieldDescription> <RadioGroup defaultValue="kubernetes" className="gap-3"> <FieldLabel htmlFor="env-k8s"> <Field orientation="horizontal"> <RadioGroupItem value="kubernetes" id="env-k8s" /> <FieldContent> <FieldTitle>Kubernetes</FieldTitle> <FieldDescription> Run GPU workloads on a K8s cluster. </FieldDescription> </FieldContent> </Field> </FieldLabel> <FieldLabel htmlFor="env-vm"> <Field orientation="horizontal"> <RadioGroupItem value="vm" id="env-vm" /> <FieldContent> <FieldTitle>Virtual Machine</FieldTitle> <FieldDescription> Access a cluster to run GPU workloads. </FieldDescription> </FieldContent> </Field> </FieldLabel> </RadioGroup> </FieldSet> )}Field Group
Stack related fields with FieldGroup, and use FieldSeparator between sections.
import { Checkbox } from "@/components/ui/checkbox"import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet,} from "@/components/ui/field"export function Example() { return ( <FieldGroup> <FieldSet> <FieldLegend variant="label">Responses</FieldLegend> <FieldDescription> Get notified when ChatGPT responds to requests that take time, like research or image generation. </FieldDescription> <FieldGroup className="gap-3"> <Field orientation="horizontal"> <Checkbox id="push-responses" defaultChecked /> <FieldLabel htmlFor="push-responses" className="font-normal"> Push notifications </FieldLabel> </Field> </FieldGroup> </FieldSet> <FieldSeparator /> <FieldSet> <FieldLegend variant="label">Tasks</FieldLegend> <FieldDescription> Get notified when tasks you've created have updates. </FieldDescription> <FieldGroup className="gap-3"> <Field orientation="horizontal"> <Checkbox id="push-tasks" /> <FieldLabel htmlFor="push-tasks" className="font-normal"> Push notifications </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="email-tasks" defaultChecked /> <FieldLabel htmlFor="email-tasks" className="font-normal"> Email notifications </FieldLabel> </Field> </FieldGroup> </FieldSet> </FieldGroup> )}Stacked Layout
By default, fields stack the label, control, and helper text. Use orientation="horizontal" when the control belongs beside the label, as with switches and checkboxes.
Provide your full name for identification.
import { Button } from "@/components/ui/button"import { Field, FieldDescription, FieldGroup, FieldLabel,} from "@/components/ui/field"import { Input } from "@/components/ui/input"import { Switch } from "@/components/ui/switch"export function Example() { return ( <FieldGroup> <Field> <FieldLabel htmlFor="stack-name">Name</FieldLabel> <Input id="stack-name" placeholder="Jane Doe" /> <FieldDescription> Provide your full name for identification. </FieldDescription> </Field> <Field> <FieldLabel htmlFor="stack-email">Email</FieldLabel> <Input id="stack-email" type="email" placeholder="jane@example.com" /> </Field> <Field orientation="horizontal"> <FieldLabel htmlFor="stack-newsletter"> Subscribe to the newsletter </FieldLabel> <Switch id="stack-newsletter" defaultChecked /> </Field> <div className="flex justify-end gap-2"> <Button type="button" variant="secondary"> Cancel </Button> <Button type="button">Submit</Button> </div> </FieldGroup> )}Validation and Errors
- Add
data-invalidtoFieldfor error styling. - Add
aria-invalidon the control. - Render
FieldErrorafter the control.
import { Field, FieldError, FieldLabel,} from "@/components/ui/field"import { Input } from "@/components/ui/input"export function Example() { return ( <Field data-invalid> <FieldLabel htmlFor="email">Email</FieldLabel> <Input id="email" type="email" defaultValue="not-an-email" aria-invalid /> <FieldError>Enter a valid email address.</FieldError> </Field> )}API Reference
FieldSet
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<FieldSet> <FieldLegend>Delivery</FieldLegend> <FieldGroup>{/* Fields */}</FieldGroup></FieldSet>FieldLegend
| Prop | Type | Default |
|---|---|---|
| variant | "legend" | "label" | "legend" |
| className | string | — |
<FieldLegend variant="label">Notification preferences</FieldLegend>FieldGroup
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<FieldGroup> <Field>{/* ... */}</Field> <Field>{/* ... */}</Field></FieldGroup>Field
| Prop | Type | Default |
|---|---|---|
| orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
| className | string | — |
| data-invalid | boolean | — |
<Field orientation="horizontal"> <FieldLabel htmlFor="remember">Remember me</FieldLabel> <Switch id="remember" /></Field>FieldContent
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<Field orientation="horizontal"> <Checkbox id="notifications" /> <FieldContent> <FieldLabel htmlFor="notifications">Notifications</FieldLabel> <FieldDescription>Email, SMS, and push options.</FieldDescription> </FieldContent></Field>FieldLabel
| Prop | Type | Default |
|---|---|---|
| className | string | — |
| htmlFor | string | — |
<FieldLabel htmlFor="email">Email</FieldLabel>FieldTitle
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<FieldContent> <FieldTitle>Enable Touch ID</FieldTitle> <FieldDescription>Unlock your device faster.</FieldDescription></FieldContent>FieldDescription
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<FieldDescription>We’ll only use this for account updates.</FieldDescription>FieldSeparator
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<FieldSeparator>Or continue with</FieldSeparator>FieldError
| Prop | Type | Default |
|---|---|---|
| errors | Array<{ message?: string } | undefined> | — |
| className | string | — |
<FieldError>Enter a valid email address.</FieldError><FieldError errors={[{ message: "Required" }]} />