---
name: field
title: Field
url: https://ui.frontic.com/components/ui/field.md
tier: ui
install: npx @frontic/ui add field
files:
  - components/ui/field/Field.vue
  - components/ui/field/FieldContent.vue
  - components/ui/field/FieldDescription.vue
  - components/ui/field/FieldError.vue
  - components/ui/field/FieldGroup.vue
  - components/ui/field/FieldLabel.vue
  - components/ui/field/FieldLegend.vue
  - components/ui/field/FieldSeparator.vue
  - components/ui/field/FieldSet.vue
  - components/ui/field/FieldTitle.vue
  - components/ui/field/FieldValidity.vue
  - components/ui/field/index.ts
  - components/ui/field/keys.ts
registryDependencies:
  - separator
dependencies:
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Field

Wraps one form control with its label, description and error, wires their ids and validates the value; most form controls join a Field placed around them.

## Install

```sh
npx @frontic/ui add field
```

Built from: [Separator](https://ui.frontic.com/components/ui/separator.md)

Used by: [Form](https://ui.frontic.com/components/ui/form.md), [Slider](https://ui.frontic.com/components/ui/slider.md), [Account Access](https://ui.frontic.com/components/sections/account-access.md), [Delivery Slot](https://ui.frontic.com/components/sections/delivery-slot.md), [Payment Method](https://ui.frontic.com/components/sections/payment-method.md), [Product Options](https://ui.frontic.com/components/sections/product-options.md), [Promo Code](https://ui.frontic.com/components/sections/promo-code.md), [Return Request](https://ui.frontic.com/components/sections/return-request.md), [Settings](https://ui.frontic.com/components/sections/settings.md), [Shipping Address](https://ui.frontic.com/components/sections/shipping-address.md), [Sign In](https://ui.frontic.com/components/sections/sign-in.md), [Checkout Page](https://ui.frontic.com/components/templates/checkout-page.md)

npm packages: `@vueuse/core`, `class-variance-authority`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { ref, useId } from 'vue'
import { Input } from '@/components/ui/input'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Switch } from '@/components/ui/switch'
import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle } from '@/components/ui/field'

const id = useId()
const updates = ref(true)
const delivery = ref('standard')
const options = [
  { value: 'standard', title: 'Standard', description: '3 to 5 working days · €4.99' },
  { value: 'express', title: 'Express', description: 'Next working day · €12.90' },
]
</script>

<template>
  <FieldGroup class="max-w-md">
    <FieldSet>
      <FieldLegend>Contact</FieldLegend>
      <FieldDescription>We send your order confirmation and tracking link here.</FieldDescription>
      <FieldGroup>
        <Field name="email" required validation-mode="onBlur">
          <FieldLabel>Email</FieldLabel>
          <Input type="email" placeholder="you@example.com" autocomplete="email" />
          <FieldError match="valueMissing">Enter your email for the order confirmation.</FieldError>
          <FieldError match="typeMismatch">Enter a full email address, like you@example.com.</FieldError>
        </Field>
        <Field orientation="horizontal">
          <FieldContent>
            <FieldLabel :for="`${id}-updates`">Order updates</FieldLabel>
            <FieldDescription>Email me when my order ships.</FieldDescription>
          </FieldContent>
          <Switch :id="`${id}-updates`" v-model="updates" />
        </Field>
      </FieldGroup>
    </FieldSet>
    <FieldSeparator />
    <FieldSet>
      <FieldLegend>Delivery</FieldLegend>
      <RadioGroup v-model="delivery">
        <FieldLabel v-for="option in options" :key="option.value" :for="`${id}-${option.value}`">
          <Field orientation="horizontal">
            <FieldContent>
              <FieldTitle>{{ option.title }}</FieldTitle>
              <FieldDescription>{{ option.description }}</FieldDescription>
            </FieldContent>
            <RadioGroupItem :id="`${id}-${option.value}`" :value="option.value" />
          </Field>
        </FieldLabel>
      </RadioGroup>
    </FieldSet>
  </FieldGroup>
</template>
```

The example also uses `input`, `radio-group` and `switch`, which `add field` leaves out: `npx @frontic/ui add input radio-group switch`.

## Anatomy

```vue
<FieldGroup>
  <FieldSet>
    <FieldLegend />
    <FieldDescription />
    <FieldGroup>
      <Field>
        <FieldLabel />
        <FieldError />
        <FieldContent>
          <FieldLabel />
          <FieldDescription />
        </FieldContent>
      </Field>
    </FieldGroup>
    <FieldLabel>
      <Field>
        <FieldContent>
          <FieldTitle />
          <FieldDescription />
        </FieldContent>
      </Field>
    </FieldLabel>
  </FieldSet>
  <FieldSeparator />
</FieldGroup>
```

## API reference

### Field

Groups a label, an input, a description and an error of one form field, and validates its input.

`data-slot="field"` · `Field.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | The name of the field. Submitted with its owning form as part of a name/value pair, and used to match server `errors` on a `FormRoot`. Takes precedence over the `name` of the control. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the field's control. Takes precedence over the `disabled` of the control. |
| `required` | `boolean` | `false` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `invalid` | `boolean` |  | When `true`, marks the field invalid regardless of its own validation. Useful when the field state is controlled by an external library. |
| `dirty` | `boolean` |  | Whether the field's value has changed from its initial value. Useful when the field state is controlled by an external library. |
| `touched` | `boolean` |  | Whether the field has been touched (its control blurred). Useful when the field state is controlled by an external library. |
| `validate` | `FieldValidateFn`: `(value: unknown, formValues: Record<string, unknown>): FieldValidateResult \| Promise<FieldValidateResult>` |  | Custom validation function. Return an error message (or array of messages) when invalid, or `null`/`undefined` when valid. Receives the control's value and the values of every named field in the owning form.<br><br>Can be async, but an async `validate` does not prevent form submission when `validationMode` is `onSubmit`. |
| `validationMode` | `FieldValidationMode`: `'onBlur' \| 'onChange' \| 'onSubmit'` | `'onSubmit'` | When the field (re-)runs validation. Takes precedence over the `validationMode` of an ancestor `FormRoot`.<br>- `onSubmit`: when the form is submitted, then on every change after that.<br>- `onBlur`: when the control loses focus.<br>- `onChange`: on every change to the control's value. |
| `validationDebounceTime` | `number` |  | How long to wait (in ms) between `validate` calls when validating on change. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `orientation` | `FieldOrientation`: `'vertical' \| 'horizontal' \| 'responsive'` | `'vertical'` | Label above the control, beside it, or `responsive`: beside it once the field group is wide enough. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ invalid: boolean; errors: string[]; }` | The label, control, description and error; gets `invalid` and the current `errors`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-horizontal` |  | Present when the part is laid out horizontally. |
| `data-orientation` | `'vertical' \| 'horizontal' \| 'responsive'` | Carries the `orientation` prop. |

### FieldContent

Groups the label and description that sit beside the control of a field. Renders a `<div>`.

`data-slot="field-content"` · `FieldContent.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### FieldDescription

Gives help text for a field, such as what to enter.

`data-slot="field-description"` · `FieldDescription.vue`

Styling: `field-description`, which extends [`hint`](https://ui.frontic.com/DESIGN.md#hint) and [`link-primary`](https://ui.frontic.com/DESIGN.md#link-primary). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  | Id of the element. Auto-generated when not provided. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### FieldError

Shows why the input of a field is invalid, from its own validation, from the server or from a form library.

`data-slot="field-error"` · `FieldError.vue`

Styling: `field-error`, which extends [`error`](https://ui.frontic.com/DESIGN.md#error). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  | Id of the element. Auto-generated when not provided. |
| `match` | `boolean \| keyof ValidityState`: `boolean \| 'badInput' \| 'customError' \| 'patternMismatch' \| 'rangeOverflow' \| 'rangeUnderflow' \| 'stepMismatch' \| 'tooLong' \| 'tooShort' \| 'typeMismatch' \| 'valid' \| 'valueMissing'` |  | Restricts when this error renders:<br>- a `ValidityState` key (e.g. `"valueMissing"`) — renders when that constraint fails.<br>- `true` — always renders, letting an external library control visibility.<br>- omitted — renders whenever the field is invalid. |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `errors` | `(string \| { message: string \| undefined; } \| undefined)[]` |  | Messages from outside the Field, such as vee-validate's or TanStack Form's; shown instead of the Field's own, duplicates dropped. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ errors: string[]; }` | Replaces the messages; gets the current `errors`. |

### FieldGroup

Stacks several fields of a form together. Renders a `<div>`.

`data-slot="field-group"` · `FieldGroup.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### FieldLabel

Labels the input of a field.

`data-slot="field-label"` · `FieldLabel.vue`

Styling: `field-label`, which extends [`label`](https://ui.frontic.com/DESIGN.md#label). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `nativeLabel` | `boolean` | `true` | Whether the label renders a native `label` element. Set to `false` when rendering another element (e.g. `as="div"`): it then drops `for`, focuses the control on click, and relies on the control's `aria-labelledby`.<br><br>Useful for button controls like `SelectTrigger`, where a native label would forward clicks and `:hover` to the button. |
| `for` | `string` |  | The id of the element the label is associated with. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### FieldLegend

Titles a set of related fields. Renders a `<legend>`.

`data-slot="field-legend"` · `FieldLegend.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'legend'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `variant` | `'label' \| 'legend'` | `'legend'` | Whether the legend reads as a section heading or as a field label. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-variant` | `'label' \| 'legend'` | Carries the `variant` prop. |

### FieldSeparator

Separates fields of a form, optionally with a short text in between. Renders a `<div>`.

`data-slot="field-separator"` · `FieldSeparator.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-content` | `'true' \| 'false'` | `true` when the separator has text in it. |

### FieldSet

Groups related fields under one legend. Renders a `<fieldset>`.

`data-slot="field-set"` · `FieldSet.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'fieldset'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### FieldTitle

Shows the title of a field next to its description. Renders a `<div>`.

`data-slot="field-title"` · `FieldTitle.vue`

Styling: `field-label`, which extends [`label`](https://ui.frontic.com/DESIGN.md#label). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### FieldValidity

Renders from the validation state of a field, such as a custom message or a strength meter.

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ validity: Omit<Record<keyof ValidityState, boolean>, "valid"> & { valid: boolean \| null; }; errors: string[]; error: string; value: unknown; initialValue: unknown; }` | Gets the field's `validity` (`valid` is `null` until there is one), its `errors`, the first `error`, the `value` validated last and the `initialValue`. |

## Exports

```ts
import { fronticFieldValidate, type FieldOrientation } from '@/components/ui/field'
```

- `fronticFieldValidate`: below
- `FieldOrientation`: `'vertical' \| 'horizontal' \| 'responsive'`

```ts
const fronticFieldValidate: InjectionKey<(name: string) => FieldValidateFn | undefined> = Symbol('fronticFieldValidate')
```

## Styling

| Part | Recipe |
| --- | --- |
| `set` | [`field-set`](https://ui.frontic.com/DESIGN.md#field-set) |
| `group` | [`field-group`](https://ui.frontic.com/DESIGN.md#field-group) |
| `content` | [`field-content`](https://ui.frontic.com/DESIGN.md#field-content) |
| `default` | [`field`](https://ui.frontic.com/DESIGN.md#field) |
| `label` | [`field-label`](https://ui.frontic.com/DESIGN.md#field-label) |
| `description` | [`field-description`](https://ui.frontic.com/DESIGN.md#field-description) |
| `error` | [`field-error`](https://ui.frontic.com/DESIGN.md#field-error) |
| `legend` | [`field-legend`](https://ui.frontic.com/DESIGN.md#field-legend) |
| `separator` | [`field-separator`](https://ui.frontic.com/DESIGN.md#field-separator) |
| `separator-content` | [`field-separator-content`](https://ui.frontic.com/DESIGN.md#field-separator-content) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |

States with a look of their own: checked and invalid.

## Source

`components/ui/field/Field.vue`

```vue
<script lang="ts">
/** Groups a label, an input, a description and an error of one form field, and validates its input. */
export default {}
</script>

<script setup lang="ts">
import type { FieldRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { FieldOrientation } from '.'
import { reactiveOmit } from '@vueuse/core'
import { FieldRoot, useForwardProps } from 'reka-ui'
import { computed, inject, provide, toRef } from 'vue'
import { fronticFieldOrientation, fronticFieldValidate } from './keys'

const ORIENTATION: Record<FieldOrientation, string> = {
  vertical: 'flex-col',
  horizontal: 'flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
  responsive:
    'flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
}

const props = withDefaults(
  defineProps<
    FieldRootProps & {
      class?: HTMLAttributes['class']
      /** Label above the control, beside it, or `responsive`: beside it once the field group is wide enough. */
      orientation?: FieldOrientation
    }
  >(),
  {
    orientation: 'vertical',
    invalid: undefined,
    dirty: undefined,
    touched: undefined,
  },
)

defineSlots<{
  /** The label, control, description and error; gets `invalid` and the current `errors`. */
  default?: (props: { invalid: boolean; errors: string[] }) => unknown
}>()

const forwarded = useForwardProps(reactiveOmit(props, 'class', 'orientation', 'validate'))

provide(fronticFieldOrientation, toRef(props, 'orientation'))

const validateFor = inject(fronticFieldValidate, undefined)
const validate = computed(() => props.validate ?? (props.name ? validateFor?.(props.name) : undefined))
</script>

<template>
  <FieldRoot
    v-slot="slotProps"
    v-bind="forwarded"
    :validate="validate"
    role="group"
    data-slot="field"
    :data-orientation="orientation"
    :data-horizontal="orientation === 'horizontal' || undefined"
    :class="['fui-field group/field', ORIENTATION[orientation], props.class]"
  >
    <slot v-bind="slotProps" />
  </FieldRoot>
</template>
```

`components/ui/field/FieldContent.vue`

```vue
<script lang="ts">
/** Groups the label and description that sit beside the control of a field. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="field-content" :class="['fui-field-content group/field-content', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/field/FieldDescription.vue`

```vue
<script lang="ts">
/** Gives help text for a field, such as what to enter. */
export default {}
</script>

<script setup lang="ts">
import type { FieldDescriptionProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { FieldDescription, injectFieldRootContext, useForwardProps } from 'reka-ui'

const props = defineProps<FieldDescriptionProps & { class?: HTMLAttributes['class'] }>()

const forwarded = useForwardProps(reactiveOmit(props, 'class'))

const field = injectFieldRootContext(null)
</script>

<template>
  <component
    :is="field ? FieldDescription : 'p'"
    v-bind="field ? { ...forwarded, as: props.as ?? 'p' } : { id: props.id }"
    data-slot="field-description"
    :class="['fui-field-description group-data-horizontal/field:text-balance nth-last-2:-mt-1', props.class]"
  >
    <slot />
  </component>
</template>
```

`components/ui/field/FieldError.vue`

```vue
<script lang="ts">
/** Shows why the input of a field is invalid, from its own validation, from the server or from a form library. */
export default {}
</script>

<script setup lang="ts">
import type { FieldErrorProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { FieldError, injectFieldRootContext, useForwardProps } from 'reka-ui'
import { computed } from 'vue'

const props = defineProps<
  FieldErrorProps & {
    class?: HTMLAttributes['class']
    /** Messages from outside the Field, such as vee-validate's or TanStack Form's; shown instead of the Field's own, duplicates dropped. */
    errors?: Array<string | { message: string | undefined } | undefined>
  }
>()

const slots = defineSlots<{
  /** Replaces the messages; gets the current `errors`. */
  default?: (props: { errors: string[] }) => unknown
}>()

const forwarded = useForwardProps(reactiveOmit(props, 'class', 'errors'))

const field = injectFieldRootContext(null)
const given = computed(() =>
  props.errors ? [...new Set(props.errors.map((error) => (typeof error === 'string' ? error : error?.message)).filter((message): message is string => !!message))] : undefined,
)
/** Given errors, or no Field to read, render a plain alert; otherwise Reka's FieldError shows the Field's messages. */
const joins = computed(() => field !== null && !given.value)
const shown = computed(() => joins.value || !!given.value?.length || !!slots.default)
const messages = (scope?: { errors: string[] }) => scope?.errors ?? given.value ?? []
</script>

<template>
  <component
    :is="joins ? FieldError : 'div'"
    v-if="shown"
    v-slot="scope"
    v-bind="joins ? forwarded : {}"
    role="alert"
    data-slot="field-error"
    :class="['fui-field-error [&>ul]:ms-4 [&>ul]:flex [&>ul]:list-disc [&>ul]:flex-col [&>ul]:gap-1', props.class]"
  >
    <slot :errors="messages(scope)">
      <ul v-if="messages(scope).length > 1">
        <li v-for="message in messages(scope)" :key="message">{{ message }}</li>
      </ul>
      <template v-else>{{ messages(scope)[0] }}</template>
    </slot>
  </component>
</template>
```

`components/ui/field/FieldGroup.vue`

```vue
<script lang="ts">
/** Stacks several fields of a form together. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { cva } from 'class-variance-authority'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { computed, useAttrs } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)
// A checkbox group passes its own data-slot; no structural variant matches an element's own attribute.
const attrs = useAttrs()
const kind = computed(() => (attrs['data-slot'] === 'checkbox-group' ? 'checkboxes' : 'fields'))

const fieldGroup = cva('fui-field-group', {
  variants: { kind: { fields: 'fui-field-group-fields', checkboxes: 'fui-field-group-checkboxes' } },
  defaultVariants: { kind: 'fields' },
})
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="field-group"
    :class="[fieldGroup({ kind }), 'group/field-group @container/field-group [&>[data-slot=field-group]]:gap-4', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/field/FieldLabel.vue`

```vue
<script lang="ts">
/** Labels the input of a field. */
export default {}
</script>

<script setup lang="ts">
import type { FieldLabelProps } from 'reka-ui'
import type { HTMLAttributes, VNode } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { FieldLabel, injectFieldRootContext, Label, useForwardProps } from 'reka-ui'
import { Fragment, inject, useSlots } from 'vue'
import Field from './Field.vue'
import { fronticFieldOrientation } from './keys'

const fieldLabel = cva('fui-field-label group/field-label peer/field-label select-none group-data-disabled/field:fui-disabled peer-disabled:cursor-not-allowed peer-disabled:fui-disabled', {
  variants: {
    layout: { default: 'fui-field-label-default', card: 'fui-field-label-card', beside: 'fui-field-label-beside' },
  },
  defaultVariants: { layout: 'default' },
})

const props = defineProps<FieldLabelProps & { class?: HTMLAttributes['class'] }>()

const forwarded = useForwardProps(reactiveOmit(props, 'class'))
const labelProps = useForwardProps(reactiveOmit(props, 'class', 'nativeLabel'))

const field = injectFieldRootContext(null)
/** Every horizontal Field holds a checkbox, radio or switch beside its label. */
const orientation = inject(fronticFieldOrientation, undefined)
const beside = () => orientation?.value === 'horizontal'
const slots = useSlots()
/** A label wrapping a Field is a choice card. */
const holdsField = (nodes: VNode[] = []): boolean => nodes.some((node) => node.type === Field || (node.type === Fragment && Array.isArray(node.children) && holdsField(node.children as VNode[])))
const card = () => holdsField(slots.default?.())
/** A choice card labels the control inside it, so only a plain label in a Field joins the Field. */
const joins = () => field !== null && !card()
</script>

<template>
  <component
    :is="joins() ? FieldLabel : Label"
    data-slot="field-label"
    v-bind="joins() ? forwarded : labelProps"
    :class="[fieldLabel({ layout: card() ? 'card' : beside() ? 'beside' : 'default' }), card() && '[&>[data-slot=field]]:p-4', props.class]"
  >
    <slot />
  </component>
</template>
```

`components/ui/field/FieldLegend.vue`

```vue
<script lang="ts">
/** Titles a set of related fields. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cva, type VariantProps } from 'class-variance-authority'

const legend = cva('fui-field-legend', {
  variants: {
    variant: { legend: 'fui-field-legend-legend', label: 'fui-field-legend-label' },
  },
  defaultVariants: { variant: 'legend' },
})

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
      /** Whether the legend reads as a section heading or as a field label. */
      variant?: VariantProps<typeof legend>['variant']
    }
  >(),
  { as: 'legend', variant: 'legend' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="field-legend" :data-variant="variant" :class="[legend({ variant }), props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/field/FieldSeparator.vue`

```vue
<script lang="ts">
/** Separates fields of a form, optionally with a short text in between. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Separator } from '@library/components/ui/separator'

const props = defineProps<{
  class?: HTMLAttributes['class']
}>()
</script>

<template>
  <div data-slot="field-separator" :data-content="!!$slots.default" :class="['fui-field-separator relative', props.class]">
    <Separator class="absolute inset-0 top-1/2" />
    <span v-if="$slots.default" class="fui-field-separator-content relative mx-auto block w-fit" data-slot="field-separator-content">
      <slot />
    </span>
  </div>
</template>
```

`components/ui/field/FieldSet.vue`

```vue
<script lang="ts">
/** Groups related fields under one legend. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'fieldset' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="field-set" :class="['fui-field-set has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/field/FieldTitle.vue`

```vue
<script lang="ts">
/** Shows the title of a field next to its description. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { inject } from 'vue'
import { fronticFieldOrientation } from './keys'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'div' },
)

/** A title in a horizontal Field sits beside its checkbox, radio or switch, as a label there does. */
const orientation = inject(fronticFieldOrientation, undefined)
const beside = () => orientation?.value === 'horizontal'
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="field-title"
    :class="['fui-field-label items-center group-data-disabled/field:fui-disabled', beside() ? 'fui-field-label-beside' : 'fui-field-label-default', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/field/FieldValidity.vue`

```vue
<script lang="ts">
/** Renders from the validation state of a field, such as a custom message or a strength meter. */
export default {}
</script>

<script setup lang="ts">
import { FieldValidity } from 'reka-ui'

defineSlots<{
  /** Gets the field's `validity` (`valid` is `null` until there is one), its `errors`, the first `error`, the `value` validated last and the `initialValue`. */
  default?: (props: { validity: Omit<Record<keyof ValidityState, boolean>, 'valid'> & { valid: boolean | null }; errors: string[]; error: string; value: unknown; initialValue: unknown }) => unknown
}>()
</script>

<template>
  <FieldValidity v-slot="state">
    <slot v-bind="state" />
  </FieldValidity>
</template>
```

`components/ui/field/index.ts`

```ts
export { default as Field } from './Field.vue'
export { default as FieldContent } from './FieldContent.vue'
export { default as FieldDescription } from './FieldDescription.vue'
export { default as FieldError } from './FieldError.vue'
export { default as FieldGroup } from './FieldGroup.vue'
export { default as FieldLabel } from './FieldLabel.vue'
export { default as FieldLegend } from './FieldLegend.vue'
export { default as FieldSeparator } from './FieldSeparator.vue'
export { default as FieldSet } from './FieldSet.vue'
export { default as FieldTitle } from './FieldTitle.vue'
export { default as FieldValidity } from './FieldValidity.vue'
export { fronticFieldValidate } from './keys'

export type FieldOrientation = 'vertical' | 'horizontal' | 'responsive'
```

`components/ui/field/keys.ts`

```ts
import type { FieldValidateFn } from 'reka-ui'
import type { InjectionKey, Ref } from 'vue'
import type { FieldOrientation } from '.'

/** How a Form validates a named Field inside it that has no `validate` of its own; undefined leaves the Field to its native checks. */
export const fronticFieldValidate: InjectionKey<(name: string) => FieldValidateFn | undefined> = Symbol('fronticFieldValidate')

/** The orientation of the Field around a label; a label beside its checkbox, radio or switch is set in sentence case. */
export const fronticFieldOrientation: InjectionKey<Ref<FieldOrientation>> = Symbol('fronticFieldOrientation')
```

## API of the items the example uses

### Input

[Full docs](https://ui.frontic.com/components/ui/input.md) · `npx @frontic/ui add input`

#### Input

Lets the user enter a single line of text. Renders a `<input>`.

Styling: `input`, which extends [`control`](https://ui.frontic.com/DESIGN.md#control) and [`control-line`](https://ui.frontic.com/DESIGN.md#control-line). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `string \| number` |  | The starting value when the field is not bound with v-model. |
| `modelValue` | `string \| number` |  | The field's value, bound with v-model. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `size` | `'md' \| 'sm'` |  | The height of the input. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `payload: string \| number` | Fires with the new value when `modelValue` changes; `v-model` binds it. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'sm'` | Carries the `size` prop. |

### Radio Group

[Full docs](https://ui.frontic.com/components/ui/radio-group.md) · `npx @frontic/ui add radio-group`

#### RadioGroup

Groups radio items so the user can pick exactly one option.

`data-slot="radio-group"` · `RadioGroup.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `modelValue` | `AcceptableValue`: `null \| string \| number \| bigint \| Record<string, any>` |  | The controlled value of the radio item to check. Can be binded as `v-model`. |
| `defaultValue` | `AcceptableValue`: `null \| string \| number \| bigint \| Record<string, any>` |  | The value of the radio item that should be checked when initially rendered.<br><br>Use when you do not need to control the state of the radio items. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with radio items. |
| `orientation` | `DataOrientation`: `'vertical' \| 'horizontal'` |  | The orientation of the component. |
| `dir` | `Direction`: `'ltr' \| 'rtl'` |  | The reading direction of the combobox when applicable.<br><br>If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `loop` | `boolean` | `true` | When `true`, keyboard navigation will loop from last item to first, and vice versa. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `name` | `string` |  | The name of the field. Submitted with its owning form as part of a name/value pair. |
| `required` | `boolean` | `false` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `payload: AcceptableValue` | Event handler called when the radio group value changes |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ modelValue: AcceptableValue \| undefined; }` | Holds the radio items; gets the selected value as `modelValue`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |

#### RadioGroupItem

Lets the user select one option of its radio group. Renders a `<button>`.

`data-slot="radio-group-item"` · `RadioGroupItem.vue`

Styling: `radio-group-item`, which extends [`choice`](https://ui.frontic.com/DESIGN.md#choice) and [`choice-dot`](https://ui.frontic.com/DESIGN.md#choice-dot). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the radio item. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `value` | `AcceptableValue`: `null \| string \| number \| bigint \| Record<string, any>` |  | The value given as data when submitted with a `name`. |
| `name` | `string` |  | The name of the field. Submitted with its owning form as part of a name/value pair. |
| `required` | `boolean` | `false` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `id` | `string` |  | The element's `id`, so a `<label for>` can name it. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-state` | `'checked' \| 'unchecked'` | The state the part is in, one value per state. |

### Switch

[Full docs](https://ui.frontic.com/components/ui/switch.md) · `npx @frontic/ui add switch`

#### Switch

Lets the user turn a single setting on or off. Renders a `<button>`.

`data-slot="switch"` · `Switch.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `boolean` |  | The state of the switch when it is initially rendered. Use when you do not need to control its state. |
| `modelValue` | `boolean \| null`: `null \| boolean` |  | The controlled state of the switch. Can be bind as `v-model`. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the switch. |
| `id` | `string` |  | The element's `id`, so a `<label for>` can name it. |
| `value` | `string` | `'on'` | The value given as data when submitted with a `name`. |
| `trueValue` | `boolean` |  | The value used when the switch is on. Defaults to `true`. |
| `falseValue` | `boolean` |  | The value used when the switch is off. Defaults to `false`. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `name` | `string` |  | The name of the field. Submitted with its owning form as part of a name/value pair. |
| `required` | `boolean` | `false` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `size` | `'md' \| 'sm'` | `'md'` | The size of the track and thumb. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `payload: boolean` | Event handler called when the value of the switch changes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `thumb` | `{ modelValue: boolean; checked: boolean; }` | Holds content inside the thumb, such as an icon; gets the current value and whether the switch is on. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-size` | `'md' \| 'sm'` | Carries the `size` prop. |
| `data-state` | `'checked' \| 'unchecked'` | The state the part is in, one value per state. |
