Field

Accessible form layout for labels, hints, and errors.

Bring labels, controls, help text, and errors together in accessible fields and groups.

Payment Method

All transactions are secure and encrypted.

Enter your 16-digit card number.

Billing Address

The billing address associated with your payment method.

tsx
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 field

Usage

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
    └── Field

Input

Choose a unique username for your account.

Must be at least 8 characters long.

tsx
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.

tsx
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.

tsx
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).

tsx
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

Address Information

We need your address to deliver your order.

tsx
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

Show these items on the desktop

Select the items you want to show on the desktop.

tsx
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

Subscription Plan

Yearly and lifetime plans offer significant savings.

tsx
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

tsx
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.

Compute Environment

Select the compute environment for your cluster.

tsx
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.

Responses

Get notified when ChatGPT responds to requests that take time, like research or image generation.

Tasks

Get notified when tasks you've created have updates.

tsx
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.

tsx
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-invalid to Field for error styling.
  • Add aria-invalid on the control.
  • Render FieldError after the control.
tsx
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

PropTypeDefault
classNamestring
<FieldSet>  <FieldLegend>Delivery</FieldLegend>  <FieldGroup>{/* Fields */}</FieldGroup></FieldSet>

FieldLegend

PropTypeDefault
variant"legend" | "label""legend"
classNamestring
  <FieldLegend variant="label">Notification preferences</FieldLegend>

FieldGroup

PropTypeDefault
classNamestring
<FieldGroup>  <Field>{/* ... */}</Field>  <Field>{/* ... */}</Field></FieldGroup>

Field

PropTypeDefault
orientation"vertical" | "horizontal" | "responsive""vertical"
classNamestring
data-invalidboolean
<Field orientation="horizontal">  <FieldLabel htmlFor="remember">Remember me</FieldLabel>  <Switch id="remember" /></Field>

FieldContent

PropTypeDefault
classNamestring
<Field orientation="horizontal">  <Checkbox id="notifications" />  <FieldContent>    <FieldLabel htmlFor="notifications">Notifications</FieldLabel>    <FieldDescription>Email, SMS, and push options.</FieldDescription>  </FieldContent></Field>

FieldLabel

PropTypeDefault
classNamestring
htmlForstring
<FieldLabel htmlFor="email">Email</FieldLabel>

FieldTitle

PropTypeDefault
classNamestring
<FieldContent>  <FieldTitle>Enable Touch ID</FieldTitle>  <FieldDescription>Unlock your device faster.</FieldDescription></FieldContent>

FieldDescription

PropTypeDefault
classNamestring
<FieldDescription>We’ll only use this for account updates.</FieldDescription>

FieldSeparator

PropTypeDefault
classNamestring
<FieldSeparator>Or continue with</FieldSeparator>

FieldError

PropTypeDefault
errorsArray<{ message?: string } | undefined>
classNamestring
<FieldError>Enter a valid email address.</FieldError>
<FieldError errors={[{ message: "Required" }]} />