---
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" },
]
---
<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-7j9-card-name-43j">
Name on Card
</FieldLabel>
<Input
id="checkout-7j9-card-name-43j"
placeholder="Evil Rabbit"
required
/>
</Field>
<Field>
<FieldLabel for="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 class="grid grid-cols-3 gap-4">
<Field>
<FieldLabel for="checkout-exp-month-ts6">Month</FieldLabel>
<Select placeholder={months[0].label}>
<SelectTrigger id="checkout-exp-month-ts6" class="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{months.map((item) => (
<SelectItem value={item.value ?? ""}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel for="checkout-7j9-exp-year-f59">Year</FieldLabel>
<Select placeholder={years[0].label}>
<SelectTrigger id="checkout-7j9-exp-year-f59" class="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{years.map((item) => (
<SelectItem value={item.value ?? ""}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel for="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" checked />
<FieldLabel
for="checkout-7j9-same-as-shipping-wgm"
class="font-normal"
>
Same as shipping address
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSet as="div">
<FieldGroup>
<Field>
<FieldLabel for="checkout-7j9-optional-comments">
Comments
</FieldLabel>
<Textarea
id="checkout-7j9-optional-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
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>
<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="true" />
<FieldError>Choose another username.</FieldError>
</Field>
<Field orientation="horizontal">
<Switch id="newsletter" />
<FieldLabel for="newsletter">Subscribe to the newsletter</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
Composition
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Select
Fieldis the core wrapper for a single field.FieldContentis a flex column that groups label and description. Not required if you have no description.- Wrap related fields with
FieldGroup, and useFieldSetwithFieldLegendfor semantic grouping.
Input
Choose a unique username for your account.
Must be at least 8 characters long.
---
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSet,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
---
<FieldSet as="div" class="w-full max-w-xs">
<FieldGroup>
<Field>
<FieldLabel for="username">Username</FieldLabel>
<Input id="username" type="text" placeholder="Max Leiter" />
<FieldDescription>
Choose a unique username for your account.
</FieldDescription>
</Field>
<Field>
<FieldLabel for="password">Password</FieldLabel>
<FieldDescription>Must be at least 8 characters long.</FieldDescription>
<Input id="password" type="password" placeholder="••••••••" />
</Field>
</FieldGroup>
</FieldSet>Textarea
Share your thoughts about our service.
---
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSet,
} from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"
---
<FieldSet as="div" class="w-full max-w-xs">
<FieldGroup>
<Field>
<FieldLabel for="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
Select your department or area of work.
---
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" },
]
---
<Field class="w-full max-w-xs">
<FieldLabel>Department</FieldLabel>
<Select placeholder={items[0].label}>
<SelectTrigger class="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem value={item.value ?? ""}>{item.label}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>Select your department or area of work.</FieldDescription>
</Field>Slider
Set your budget range ($200 - 800).
---
import { Field, FieldDescription, FieldTitle } from "@/components/ui/field"
import { Slider } from "@/components/ui/slider"
const value = [200, 800]
---
<Field class="w-full max-w-xs" data-field-slider>
<FieldTitle data-slider-label>Price Range</FieldTitle>
<!-- prettier-ignore -->
<FieldDescription>
Set your budget range ($<span class="font-medium tabular-nums" data-field-slider-min>{value[0]}</span> - <span class="font-medium tabular-nums" data-field-slider-max>{value[1]}</span>).
</FieldDescription>
<Slider
defaultValue={[value[0], value[1]]}
max={1000}
min={0}
step={10}
class="mt-2 w-full"
/>
</Field>
<script>
const initialize = () => {
for (const root of document.querySelectorAll("[data-field-slider]")) {
if (root.hasAttribute("data-bound")) continue
root.setAttribute("data-bound", "")
// Name each thumb after the visible label: the thumbs are the
// focusable sliders, not the root.
const name = root
.querySelector("[data-slider-label]")
?.textContent?.trim()
const thumbs = root.querySelectorAll('[data-slot="slider-thumb"]')
thumbs.forEach((thumb, index) =>
thumb.setAttribute(
"aria-label",
thumbs.length > 1
? `${name} ${index === 0 ? "minimum" : "maximum"}`
: `${name}`
)
)
const min = root.querySelector("[data-field-slider-min]")
const max = root.querySelector("[data-field-slider-max]")
root
.querySelector('[data-slot="slider"]')
?.addEventListener("slider:change", (event) => {
const [start, end] = [
(event as CustomEvent<{ value: number | number[] }>).detail.value,
].flat()
if (min) min.textContent = String(start)
if (max) max.textContent = String(end)
})
}
}
initialize()
document.addEventListener("astro:page-load", initialize)
</script>Fieldset
---
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
---
<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="street">Street Address</FieldLabel>
<Input id="street" type="text" placeholder="123 Main St" />
</Field>
<div class="grid grid-cols-2 gap-4">
<Field>
<FieldLabel for="city">City</FieldLabel>
<Input id="city" type="text" placeholder="New York" />
</Field>
<Field>
<FieldLabel for="zip">Postal Code</FieldLabel>
<Input id="zip" 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"
---
<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="finder-pref-9k2-hard-disks-ljj" />
<FieldLabel for="finder-pref-9k2-hard-disks-ljj" class="font-normal">
Hard disks
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-external-disks-1yg" />
<FieldLabel
for="finder-pref-9k2-external-disks-1yg"
class="font-normal"
>
External disks
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-cds-dvds-fzt" />
<FieldLabel for="finder-pref-9k2-cds-dvds-fzt" class="font-normal">
CDs, DVDs, and iPods
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-connected-servers-6l2" />
<FieldLabel
for="finder-pref-9k2-connected-servers-6l2"
class="font-normal"
>
Connected servers
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-sync-folders-nep" checked />
<FieldContent>
<FieldLabel for="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"
---
<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="plan-monthly" />
<FieldLabel for="plan-monthly" class="font-normal">
Monthly ($9.99/month)
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="yearly" id="plan-yearly" />
<FieldLabel for="plan-yearly" class="font-normal">
Yearly ($99.99/year)
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="lifetime" id="plan-lifetime" />
<FieldLabel for="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"
---
<Field orientation="horizontal" class="w-fit">
<FieldLabel for="2fa">Multi-factor authentication</FieldLabel>
<Switch id="2fa" />
</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"
---
<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>
<Field as="span" orientation="horizontal">
<FieldContent as="span">
<FieldTitle as="span">Kubernetes</FieldTitle>
<FieldDescription as="span">
Run GPU workloads on a K8s cluster.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="kubernetes" id="kubernetes-r2h" />
</Field>
</FieldLabel>
<FieldLabel>
<Field as="span" orientation="horizontal">
<FieldContent as="span">
<FieldTitle as="span">Virtual Machine</FieldTitle>
<FieldDescription as="span">
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. Add FieldSeparator to divide them.
Get notified when ChatGPT responds to requests that take time, like research or image generation.
Get notified when tasks you've created have updates. Manage tasks
---
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
FieldSet,
} from "@/components/ui/field"
---
<FieldGroup class="w-full max-w-xs">
<FieldSet as="div">
<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" checked disabled />
<FieldLabel for="push" class="font-normal">
Push notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet as="div">
<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 for="push-tasks" class="font-normal">
Push notifications
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="email-tasks" />
<FieldLabel for="email-tasks" class="font-normal">
Email notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
</FieldGroup>RTL
---
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" },
]
const 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: "בטל",
},
},
}
const { dir, values: t } = 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
}
---
<div class="w-full max-w-md py-6" dir={dir}>
<form>
<FieldGroup>
<FieldSet>
<FieldLegend>{t.paymentMethod}</FieldLegend>
<FieldDescription>{t.secureTransactions}</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel for="checkout-7j9-card-name-43j-rtl">
{t.nameOnCard}
</FieldLabel>
<Input
id="checkout-7j9-card-name-43j-rtl"
placeholder="Evil Rabbit"
required
/>
</Field>
<Field>
<FieldLabel for="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 class="grid grid-cols-3 gap-4">
<Field>
<FieldLabel for="checkout-exp-month-ts6-rtl">
{t.month}
</FieldLabel>
<Select placeholder={getMonthLabel(null)}>
<SelectTrigger id="checkout-exp-month-ts6-rtl" class="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent dir={dir}>
<SelectGroup>
{months.map((item) => (
<SelectItem value={item.value ?? ""}>
{getMonthLabel(item.value)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel for="checkout-7j9-exp-year-f59-rtl">
{t.year}
</FieldLabel>
<Select placeholder={years[0].label}>
<SelectTrigger
id="checkout-7j9-exp-year-f59-rtl"
class="w-full"
>
<SelectValue />
</SelectTrigger>
<SelectContent dir={dir}>
<SelectGroup>
{years.map((item) => (
<SelectItem value={item.value ?? ""}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel for="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" checked />
<FieldLabel
for="checkout-7j9-same-as-shipping-wgm-rtl"
class="font-normal"
>
{t.sameAsShipping}
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSet as="div">
<FieldGroup>
<Field>
<FieldLabel for="checkout-7j9-optional-comments-rtl">
{t.comments}
</FieldLabel>
<Textarea
id="checkout-7j9-optional-comments-rtl"
placeholder={t.commentsPlaceholder}
class="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: the default orientation stacks label, control, and helper text, ideal 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"to switch from a column to a row when the parentFieldGroupis at least@mdwide.FieldGroupis already a@container/field-group.
---
import { Button } from "@/components/ui/button"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
---
<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="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-invalid="true"toFieldto switch the entire block into an error state. - Add
aria-invalid="true"on the input itself for assistive technologies. - Render
FieldErrorimmediately after the control or insideFieldContentto keep error messages aligned with the field.
<Field data-invalid="true">
<FieldLabel for="email">Email</FieldLabel>
<Input id="email" type="email" aria-invalid="true" />
<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.FieldErrorrenders withrole="alert", and only when it has content.- Apply
FieldSeparatorsparingly to ensure screen readers encounter clear section boundaries.
API Reference
Source: src/components/ui/field
All parts accept class and the attributes of the element they render.
| Component | Prop | Type | Default |
|---|---|---|---|
Field | orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
FieldLegend | variant | "legend" | "label" | "legend" |
FieldError | errors | Array<{ message?: string } | undefined> |
| Component | Renders |
|---|---|
FieldSet | fieldset |
FieldLegend | legend with data-variant |
FieldGroup | div, a @container/field-group |
Field | div with role="group" and data-orientation |
FieldContent | div flex column for label and description beside a control |
FieldLabel | Label (label), pass for to link the control |
FieldTitle | div with label styling, for a title that is not a label |
FieldDescription | p helper text |
FieldSeparator | div with a Separator, plus centered text when it has content (data-content) |
FieldError | div with role="alert": the slot, the single message, or a list of unique errors messages |