---
type: doc
title: Field
description: Combine labels, controls, and help text to compose accessible form fields and grouped inputs.
---

> Fulldev UI for Astro. The `@fulldev` registry in `components.json` points at `https://ui.full.dev/r/styles/{style}/{name}.json`; see [installation](/astro/docs/installation.md).

```astro title="src/components/examples/field/field-demo.astro"
---
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

```bash
npx shadcn@latest add @fulldev/field
```

## Usage

```ts
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSeparator,
  FieldSet,
  FieldTitle,
} from "@/components/ui/field"
```

```astro
<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

```text
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
    ├── Field
    │   ├── FieldLabel
    │   ├── Input / Textarea / Switch / Select
    │   ├── FieldDescription
    │   └── FieldError
    ├── FieldSeparator
    └── Field
        ├── FieldLabel
        └── Input / Textarea / Switch / Select
```

- `Field` is the core wrapper for a single field.
- `FieldContent` is a flex column that groups label and description. Not required if you have no description.
- Wrap related fields with `FieldGroup`, and use `FieldSet` with `FieldLegend` for semantic grouping.

## Input

```astro title="src/components/examples/field/field-input.astro"
---
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

```astro title="src/components/examples/field/field-textarea.astro"
---
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

```astro title="src/components/examples/field/field-select.astro"
---
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

```astro title="src/components/examples/field/field-slider.astro"
---
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

```astro title="src/components/examples/field/field-fieldset.astro"
---
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

```astro title="src/components/examples/field/field-checkbox.astro"
---
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

```astro title="src/components/examples/field/field-radio.astro"
---
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

```astro title="src/components/examples/field/field-switch.astro"
---
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.

```astro title="src/components/examples/field/field-choice-card.astro"
---
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.

```astro title="src/components/examples/field/field-group.astro"
---
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&apos;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

```astro title="src/components/examples/field/field-rtl.astro"
---
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"` on `Field` to align the label and control side by side. Pair with `FieldContent` to keep descriptions aligned.
- **Responsive fields:** set `orientation="responsive"` to switch from a column to a row when the parent `FieldGroup` is at least `@md` wide. `FieldGroup` is already a `@container/field-group`.

```astro title="src/components/examples/field/field-responsive.astro"
---
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"` to `Field` to switch the entire block into an error state.
- Add `aria-invalid="true"` on the input itself for assistive technologies.
- Render `FieldError` immediately after the control or inside `FieldContent` to keep error messages aligned with the field.

```astro
<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

- `FieldSet` and `FieldLegend` keep related controls grouped for keyboard and assistive tech users.
- `Field` outputs `role="group"` so nested controls inherit labeling from `FieldLabel` and `FieldLegend` when combined.
- `FieldError` renders with `role="alert"`, and only when it has content.
- Apply `FieldSeparator` sparingly to ensure screen readers encounter clear section boundaries.

## API Reference

Source: [`src/components/ui/field`](https://github.com/fulldotdev/ui/tree/main/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 |

## Install what this page shows

```bash
npx shadcn@latest add @fulldev/field-examples
```
