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,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
const months = [
{ label: "MM", value: null },
{ 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: "YYYY", value: null },
{ 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 function FieldDemo() {
return (
<div className="w-full max-w-md">
<form>
<FieldGroup>
<FieldSet>
<FieldLegend>Payment Method</FieldLegend>
<FieldDescription>
All transactions are secure and encrypted
</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-card-name-43j">
Name on Card
</FieldLabel>
<Input
id="checkout-7j9-card-name-43j"
placeholder="Evil Rabbit"
required
/>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-card-number-uw1">
Card Number
</FieldLabel>
<Input
id="checkout-7j9-card-number-uw1"
placeholder="1234 5678 9012 3456"
required
/>
<FieldDescription>
Enter your 16-digit card number
</FieldDescription>
</Field>
<div className="grid grid-cols-3 gap-4">
<Field>
<FieldLabel htmlFor="checkout-exp-month-ts6">
Month
</FieldLabel>
<Select items={months}>
<SelectTrigger id="checkout-exp-month-ts6">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{months.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-exp-year-f59">
Year
</FieldLabel>
<Select items={years}>
<SelectTrigger id="checkout-7j9-exp-year-f59">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{years.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-cvv">CVV</FieldLabel>
<Input id="checkout-7j9-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-7j9-same-as-shipping-wgm"
defaultChecked
/>
<FieldLabel
htmlFor="checkout-7j9-same-as-shipping-wgm"
className="font-normal"
>
Same as shipping address
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSet>
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-optional-comments">
Comments
</FieldLabel>
<Textarea
id="checkout-7j9-optional-comments"
placeholder="Add any additional comments"
className="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
npx shadcn@latest add @fulldev/field
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 htmlFor="name">Full name</FieldLabel>
<Input id="name" autoComplete="off" placeholder="Evil Rabbit" />
<FieldDescription>This appears on invoices and emails.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="username">Username</FieldLabel>
<Input id="username" autoComplete="off" aria-invalid />
<FieldError>Choose another username.</FieldError>
</Field>
<Field orientation="horizontal">
<Switch id="newsletter" />
<FieldLabel htmlFor="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
└── FieldError
FieldGroup
Related fields in one group.
FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Select
FieldSet
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 / Select
Anatomy
The Field family is designed for composing accessible forms.
<Field>
<FieldLabel htmlFor="input-id">Label</FieldLabel>
{/* Input, Select, Switch, etc. */}
<FieldDescription>Optional helper text.</FieldDescription>
<FieldError>Validation message.</FieldError>
</Field>
Form
See the Form documentation for building forms with the Field component and React Hook Form, Tanstack Form, or Formisch.
Input
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSet,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
export function FieldInput() {
return (
<FieldSet className="w-full max-w-xs">
<FieldGroup>
<Field>
<FieldLabel htmlFor="username">Username</FieldLabel>
<Input id="username" type="text" placeholder="Max Leiter" />
<FieldDescription>
Choose a unique username for your account.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="password">Password</FieldLabel>
<FieldDescription>
Must be at least 8 characters long.
</FieldDescription>
<Input id="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 function FieldTextarea() {
return (
<FieldSet className="w-full max-w-xs">
<FieldGroup>
<Field>
<FieldLabel htmlFor="feedback">Feedback</FieldLabel>
<Textarea
id="feedback"
placeholder="Your feedback helps us improve..."
rows={4}
/>
<FieldDescription>
Share your thoughts about our service.
</FieldDescription>
</Field>
</FieldGroup>
</FieldSet>
)
}Select
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Choose department", value: null },
{ 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 function FieldSelect() {
return (
<Field className="w-full max-w-xs">
<FieldLabel>Department</FieldLabel>
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>
Select your department or area of work.
</FieldDescription>
</Field>
)
}Slider
"use client"
import * as React from "react"
import { Field, FieldDescription, FieldTitle } from "@/components/ui/field"
import { Slider } from "@/components/ui/slider"
export function FieldSlider() {
const [value, setValue] = React.useState([200, 800])
return (
<Field className="w-full max-w-xs">
<FieldTitle>Price Range</FieldTitle>
<FieldDescription>
Set your budget range ($
<span className="font-medium tabular-nums">{value[0]}</span> -{" "}
<span className="font-medium tabular-nums">{value[1]}</span>).
</FieldDescription>
<Slider
value={value}
onValueChange={(value) => setValue(value as [number, number])}
max={1000}
min={0}
step={10}
className="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 function FieldFieldset() {
return (
<FieldSet className="w-full max-w-sm">
<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" type="text" placeholder="123 Main St" />
</Field>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="city">City</FieldLabel>
<Input id="city" type="text" placeholder="New York" />
</Field>
<Field>
<FieldLabel htmlFor="zip">Postal Code</FieldLabel>
<Input id="zip" type="text" placeholder="90502" />
</Field>
</div>
</FieldGroup>
</FieldSet>
)
}Checkbox
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
} from "@/components/ui/field"
export function FieldCheckbox() {
return (
<FieldGroup className="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 className="gap-3">
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-hard-disks-ljj" />
<FieldLabel
htmlFor="finder-pref-9k2-hard-disks-ljj"
className="font-normal"
defaultChecked
>
Hard disks
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-external-disks-1yg" />
<FieldLabel
htmlFor="finder-pref-9k2-external-disks-1yg"
className="font-normal"
>
External disks
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-cds-dvds-fzt" />
<FieldLabel
htmlFor="finder-pref-9k2-cds-dvds-fzt"
className="font-normal"
>
CDs, DVDs, and iPods
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-connected-servers-6l2" />
<FieldLabel
htmlFor="finder-pref-9k2-connected-servers-6l2"
className="font-normal"
>
Connected servers
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-sync-folders-nep" defaultChecked />
<FieldContent>
<FieldLabel htmlFor="finder-pref-9k2-sync-folders-nep">
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 function FieldRadio() {
return (
<FieldSet className="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="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 FieldSwitch() {
return (
<Field orientation="horizontal" className="w-fit">
<FieldLabel htmlFor="2fa">Multi-factor authentication</FieldLabel>
<Switch id="2fa" />
</Field>
)
}Choice Card
Wrap Field components inside FieldLabel to create selectable field groups.
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export function FieldChoiceCard() {
return (
<FieldGroup className="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 htmlFor="kubernetes-r2h">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Kubernetes</FieldTitle>
<FieldDescription>
Run GPU workloads on a K8s cluster.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="kubernetes" id="kubernetes-r2h" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="vm-z4k">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Virtual Machine</FieldTitle>
<FieldDescription>
Access a cluster to run GPU workloads.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="vm" id="vm-z4k" />
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
</FieldGroup>
)
}Field Group
Stack Field components with FieldGroup.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
FieldSet,
} from "@/components/ui/field"
export function FieldGroupExample() {
return (
<FieldGroup className="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="push" defaultChecked disabled />
<FieldLabel htmlFor="push" className="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="#">Manage tasks</a>
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
<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" />
<FieldLabel htmlFor="email-tasks" className="font-normal">
Email notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
</FieldGroup>
)
}RTL
To enable RTL support in shadcn/ui, see the RTL configuration guide.
"use client"
import * as React from "react"
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,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import {
useTranslation,
type Translations,
} from "@/components/language-selector"
const months = [
{ label: "MM", value: null },
{ 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: "YYYY", value: null },
{ label: "2024", value: "2024" },
{ label: "2025", value: "2025" },
{ label: "2026", value: "2026" },
{ label: "2027", value: "2027" },
{ label: "2028", value: "2028" },
{ label: "2029", value: "2029" },
]
const translations: Translations = {
en: {
dir: "ltr",
values: {
paymentMethod: "Payment Method",
secureTransactions: "All transactions are secure and encrypted",
nameOnCard: "Name on Card",
cardNumber: "Card Number",
cardNumberDescription: "Enter your 16-digit card number",
month: "Month",
year: "Year",
cvv: "CVV",
monthPlaceholder: "MM",
month01: "01",
month02: "02",
month03: "03",
month04: "04",
month05: "05",
month06: "06",
month07: "07",
month08: "08",
month09: "09",
month10: "10",
month11: "11",
month12: "12",
billingAddress: "Billing Address",
billingAddressDescription:
"The billing address associated with your payment method",
sameAsShipping: "Same as shipping address",
comments: "Comments",
commentsPlaceholder: "Add any additional comments",
submit: "Submit",
cancel: "Cancel",
},
},
ar: {
dir: "rtl",
values: {
paymentMethod: "طريقة الدفع",
secureTransactions: "جميع المعاملات آمنة ومشفرة",
nameOnCard: "الاسم على البطاقة",
cardNumber: "رقم البطاقة",
cardNumberDescription: "أدخل رقم البطاقة المكون من 16 رقمًا",
month: "الشهر",
year: "السنة",
cvv: "CVV",
monthPlaceholder: "ش.ش",
month01: "٠١",
month02: "٠٢",
month03: "٠٣",
month04: "٠٤",
month05: "٠٥",
month06: "٠٦",
month07: "٠٧",
month08: "٠٨",
month09: "٠٩",
month10: "١٠",
month11: "١١",
month12: "١٢",
billingAddress: "عنوان الفوترة",
billingAddressDescription: "عنوان الفوترة المرتبط بطريقة الدفع الخاصة بك",
sameAsShipping: "نفس عنوان الشحن",
comments: "تعليقات",
commentsPlaceholder: "أضف أي تعليقات إضافية",
submit: "إرسال",
cancel: "إلغاء",
},
},
he: {
dir: "rtl",
values: {
paymentMethod: "אמצעי תשלום",
secureTransactions: "כל העסקאות מאובטחות ומוצפנות",
nameOnCard: "שם על הכרטיס",
cardNumber: "מספר כרטיס",
cardNumberDescription: "הזן את מספר הכרטיס בן 16 הספרות שלך",
month: "חודש",
year: "שנה",
cvv: "CVV",
monthPlaceholder: "MM",
month01: "01",
month02: "02",
month03: "03",
month04: "04",
month05: "05",
month06: "06",
month07: "07",
month08: "08",
month09: "09",
month10: "10",
month11: "11",
month12: "12",
billingAddress: "כתובת חיוב",
billingAddressDescription: "כתובת החיוב המשויכת לאמצעי התשלום שלך",
sameAsShipping: "זהה לכתובת המשלוח",
comments: "הערות",
commentsPlaceholder: "הוסף הערות נוספות",
submit: "שלח",
cancel: "בטל",
},
},
}
export function FieldRtl() {
const { dir, t } = useTranslation(translations, "ar")
const getMonthLabel = (value: string | null): string => {
if (value === null) return t.monthPlaceholder
const monthKey = `month${value}` as keyof typeof t
return t[monthKey] || value
}
return (
<div className="w-full max-w-md py-6" dir={dir}>
<form>
<FieldGroup>
<FieldSet>
<FieldLegend>{t.paymentMethod}</FieldLegend>
<FieldDescription>{t.secureTransactions}</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-card-name-43j-rtl">
{t.nameOnCard}
</FieldLabel>
<Input
id="checkout-7j9-card-name-43j-rtl"
placeholder="Evil Rabbit"
required
/>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-card-number-uw1-rtl">
{t.cardNumber}
</FieldLabel>
<Input
id="checkout-7j9-card-number-uw1-rtl"
placeholder="1234 5678 9012 3456"
required
/>
<FieldDescription>{t.cardNumberDescription}</FieldDescription>
</Field>
<div className="grid grid-cols-3 gap-4">
<Field>
<FieldLabel htmlFor="checkout-exp-month-ts6-rtl">
{t.month}
</FieldLabel>
<Select items={months}>
<SelectTrigger id="checkout-exp-month-ts6-rtl">
<SelectValue />
</SelectTrigger>
<SelectContent dir={dir}>
<SelectGroup>
{months.map((item) => (
<SelectItem key={item.value} value={item.value}>
{getMonthLabel(item.value)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-exp-year-f59-rtl">
{t.year}
</FieldLabel>
<Select items={years}>
<SelectTrigger id="checkout-7j9-exp-year-f59-rtl">
<SelectValue />
</SelectTrigger>
<SelectContent dir={dir}>
<SelectGroup>
{years.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="checkout-7j9-cvv-rtl">
{t.cvv}
</FieldLabel>
<Input id="checkout-7j9-cvv-rtl" placeholder="123" required />
</Field>
</div>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLegend>{t.billingAddress}</FieldLegend>
<FieldDescription>{t.billingAddressDescription}</FieldDescription>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox
id="checkout-7j9-same-as-shipping-wgm-rtl"
defaultChecked
/>
<FieldLabel
htmlFor="checkout-7j9-same-as-shipping-wgm-rtl"
className="font-normal"
>
{t.sameAsShipping}
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSet>
<FieldGroup>
<Field>
<FieldLabel htmlFor="checkout-7j9-optional-comments-rtl">
{t.comments}
</FieldLabel>
<Textarea
id="checkout-7j9-optional-comments-rtl"
placeholder={t.commentsPlaceholder}
className="resize-none"
/>
</Field>
</FieldGroup>
</FieldSet>
<Field orientation="horizontal">
<Button type="submit">{t.submit}</Button>
<Button variant="outline" type="button">
{t.cancel}
</Button>
</Field>
</FieldGroup>
</form>
</div>
)
}Responsive Layout
- Vertical fields: Default orientation stacks label, control, and helper text, ideal for mobile-first layouts.
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 function FieldResponsive() {
return (
<div className="w-full max-w-lg">
<form>
<FieldSet>
<FieldLegend>Profile</FieldLegend>
<FieldDescription>Fill in your profile information.</FieldDescription>
<FieldGroup>
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="name">Name</FieldLabel>
<FieldDescription>
Provide your full name for identification
</FieldDescription>
</FieldContent>
<Input id="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.
<Field data-invalid>
<FieldLabel htmlFor="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 to ensure screen readers encounter clear section boundaries.
API Reference
Source: react/src/components/ui/field.tsx
FieldSet
Container that renders a semantic fieldset with spacing presets.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSet>
<FieldLegend>Delivery</FieldLegend>
<FieldGroup>{/* Fields */}</FieldGroup>
</FieldSet>
FieldLegend
Legend element for a FieldSet.
| Prop | Type | Default |
|---|---|---|
variant | "legend" | "label" | "legend" |
className | string |
<FieldLegend variant="label">Notification Preferences</FieldLegend>
FieldGroup
Layout wrapper that stacks Field components and enables container queries for responsive orientations.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldGroup className="@container/field-group flex flex-col gap-6">
<Field>{/* ... */}</Field>
<Field>{/* ... */}</Field>
</FieldGroup>
Field
The core wrapper for a single 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
Flex column that groups control and descriptions when the label sits beside the control.
| Prop | Type | Default |
|---|---|---|
className | string |
<Field>
<Checkbox id="notifications" />
<FieldContent>
<FieldLabel htmlFor="notifications">Notifications</FieldLabel>
<FieldDescription>Email, SMS, and push options.</FieldDescription>
</FieldContent>
</Field>
FieldLabel
Label styled for both direct inputs and nested Field children.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldLabel htmlFor="email">Email</FieldLabel>
FieldTitle
Renders a title with label styling inside FieldContent.
| Prop | Type | Default |
|---|---|---|
className | string |
<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.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldDescription>We never share your email with anyone.</FieldDescription>
FieldSeparator
Visual divider to separate sections inside a FieldGroup.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSeparator>Or continue with</FieldSeparator>
FieldError
Accessible error container that accepts children or an errors array (e.g., from react-hook-form).
| Prop | Type | Default |
|---|---|---|
errors | Array<{ message?: string } | undefined> | |
className | string |
<FieldError errors={errors.username} />