---
name: radio-accordion
title: Radio Accordion
url: https://ui.frontic.com/components/ui/radio-accordion.md
tier: ui
install: npx @frontic/ui add radio-accordion
files:
  - components/ui/radio-accordion/RadioAccordion.vue
  - components/ui/radio-accordion/RadioAccordionContent.vue
  - components/ui/radio-accordion/RadioAccordionIndicator.vue
  - components/ui/radio-accordion/RadioAccordionItem.vue
  - components/ui/radio-accordion/RadioAccordionTrigger.vue
  - components/ui/radio-accordion/index.ts
  - components/ui/radio-accordion/keys.ts
registryDependencies:
  - collapsible
  - use-field-control
dependencies:
  - "@lucide/vue"
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Radio Accordion

Radio options where the picked one reveals its details, for checkout choices like a card form under the card option or a delivery window under express shipping.

## Install

```sh
npx @frontic/ui add radio-accordion
```

Built from: [Collapsible](https://ui.frontic.com/components/ui/collapsible.md), [Use Field Control](https://ui.frontic.com/composables/use-field-control.md)

Used by: [Payment Method](https://ui.frontic.com/components/sections/payment-method.md), [Checkout Page](https://ui.frontic.com/components/templates/checkout-page.md)

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

## Examples

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { Field, FieldLabel } from '@/components/ui/field'
import { NativeSelect } from '@/components/ui/native-select'
import { RadioAccordion, RadioAccordionContent, RadioAccordionItem, RadioAccordionTrigger } from '@/components/ui/radio-accordion'

const shipping = ref('express')
const window = ref('morning')
</script>

<template>
  <RadioAccordion v-model="shipping" class="w-md max-w-full">
    <RadioAccordionItem value="standard">
      <RadioAccordionTrigger>Standard, 3 to 5 working days</RadioAccordionTrigger>
      <RadioAccordionContent>Free on orders over €50, otherwise €4.90.</RadioAccordionContent>
    </RadioAccordionItem>
    <RadioAccordionItem value="express">
      <RadioAccordionTrigger>Express, next working day</RadioAccordionTrigger>
      <RadioAccordionContent>
        <Field>
          <FieldLabel>Delivery window</FieldLabel>
          <NativeSelect v-model="window" class="w-full">
            <option value="morning">8:00 to 12:00</option>
            <option value="afternoon">12:00 to 17:00</option>
            <option value="evening">17:00 to 21:00</option>
          </NativeSelect>
        </Field>
      </RadioAccordionContent>
    </RadioAccordionItem>
    <RadioAccordionItem value="pickup" disabled>
      <RadioAccordionTrigger>Pick up at the Basecamp store</RadioAccordionTrigger>
    </RadioAccordionItem>
  </RadioAccordion>
</template>
```

The example also uses `field` and `native-select`, which `add radio-accordion` leaves out: `npx @frontic/ui add field native-select`.

## Anatomy

```vue
<RadioAccordion>
  <RadioAccordionItem>
    <RadioAccordionTrigger />
    <RadioAccordionContent />
  </RadioAccordionItem>
</RadioAccordion>
```

## API reference

### RadioAccordion

Groups options of which the user picks one; the picked option reveals its details.

`data-slot="radio-accordion"` · `RadioAccordion.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. |
| `variant` | `RadioAccordionVariant`: `'outline' \| 'plain'` | `'plain'` | Whether the options stack into one bordered list or stand apart as framed cards. |

| 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 options; gets the selected value as `modelValue`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-variant` | `'outline' \| 'plain'` | Carries the `variant` prop. |

### RadioAccordionItem

Groups one option with the details it reveals while selected. Renders a `<div>`.

`data-slot="radio-accordion-item"` · `RadioAccordionItem.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. |
| `value` (required) | `AcceptableValue`: `null \| string \| number \| bigint \| Record<string, any>` |  | The value the group takes when this option is picked. |
| `disabled` | `boolean` | `false` | Keeps the user from picking this option. |

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

### RadioAccordionTrigger

Labels its option and lets the user pick it. Renders a `<button>`.

`data-slot="radio-accordion-trigger"` · `RadioAccordionTrigger.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` | `'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. |
| `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. |

### RadioAccordionContent

Holds the details its option reveals while selected, such as a card form or a delivery window. Renders a `<div>`.

`data-slot="radio-accordion-content"` · `RadioAccordionContent.vue`

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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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. |
| `unmountOnHide` | `boolean` | `true` | Removes the details from the page while another option is picked; set false to keep them, e.g. a payment provider's card iframe, and mind that hidden required fields still block the form. |

## Exports

```ts
import { type RadioAccordionVariant } from '@/components/ui/radio-accordion'
```

- `RadioAccordionVariant`: `'plain' \| 'outline'`

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`radio-accordion`](https://ui.frontic.com/DESIGN.md#radio-accordion) |
| `item` | [`radio-accordion-item`](https://ui.frontic.com/DESIGN.md#radio-accordion-item) |
| `trigger` | [`radio-accordion-trigger`](https://ui.frontic.com/DESIGN.md#radio-accordion-trigger) |
| `content` | [`radio-accordion-content`](https://ui.frontic.com/DESIGN.md#radio-accordion-content) |
| `indicator` | [`radio-accordion-indicator`](https://ui.frontic.com/DESIGN.md#radio-accordion-indicator) |
| `focus-ring` | [`focus-ring`](https://ui.frontic.com/DESIGN.md#focus-ring) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |

States with a look of their own: focus, checked and disabled.

## Source

`components/ui/radio-accordion/RadioAccordion.vue`

```vue
<script lang="ts">
/** Groups options of which the user picks one; the picked option reveals its details. */
export default {}
</script>

<script setup lang="ts">
import type { AcceptableValue, RadioGroupRootEmits, RadioGroupRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { RadioAccordionVariant } from './keys'
import { reactiveOmit } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { RadioGroupRoot, useForwardPropsEmits } from 'reka-ui'
import { provide, toRef, useTemplateRef } from 'vue'
import { checkedRadio, useFieldControl } from '@library/composables/use-field-control'
import { radioAccordionVariant } from './keys'

const radioAccordion = cva('fui-radio-accordion', {
  variants: {
    variant: { plain: 'fui-radio-accordion-plain', outline: 'fui-radio-accordion-outline' },
  },
  defaultVariants: { variant: 'plain' },
})

const props = withDefaults(
  defineProps<
    RadioGroupRootProps & {
      class?: HTMLAttributes['class']
      /** Whether the options stack into one bordered list or stand apart as framed cards. */
      variant?: RadioAccordionVariant
    }
  >(),
  { variant: 'plain' },
)
const emits = defineEmits<RadioGroupRootEmits>()

const forwarded = useForwardPropsEmits(reactiveOmit(props, 'class', 'variant'), emits)

defineSlots<{
  /** Holds the options; gets the selected value as `modelValue`. */
  default?: (props: { modelValue: AcceptableValue | undefined }) => unknown
}>()

// Delete once Reka's RadioGroup reads the Field itself.
const { joined, report } = useFieldControl({
  props,
  forwarded,
  root: useTemplateRef<{ $el: HTMLElement }>('root'),
  element: checkedRadio,
})

provide(
  radioAccordionVariant,
  toRef(() => props.variant),
)
</script>

<template>
  <RadioGroupRoot ref="root" v-slot="slotProps" data-slot="radio-accordion" :data-variant="variant" :class="[radioAccordion({ variant }), props.class]" v-bind="joined" @update:model-value="report">
    <slot v-bind="slotProps" />
  </RadioGroupRoot>
</template>
```

`components/ui/radio-accordion/RadioAccordionContent.vue`

```vue
<script lang="ts">
/** Holds the details its option reveals while selected, such as a card form or a delivery window. */
export default {}
</script>

<script setup lang="ts">
import type { CollapsibleContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { inject } from 'vue'
import { Collapsible, CollapsibleContent } from '@library/components/ui/collapsible'
import { radioAccordionItem } from './keys'

const props = withDefaults(
  defineProps<
    CollapsibleContentProps & {
      class?: HTMLAttributes['class']
      /** Removes the details from the page while another option is picked; set false to keep them, e.g. a payment provider's card iframe, and mind that hidden required fields still block the form. */
      unmountOnHide?: boolean
    }
  >(),
  { unmountOnHide: true },
)

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

const item = inject(radioAccordionItem)!
</script>

<template>
  <Collapsible :open="item.checked.value" :unmount-on-hide="unmountOnHide">
    <CollapsibleContent v-bind="forwarded">
      <div data-slot="radio-accordion-content" :class="['fui-radio-accordion-content', props.class]">
        <slot />
      </div>
    </CollapsibleContent>
  </Collapsible>
</template>
```

`components/ui/radio-accordion/RadioAccordionIndicator.vue`

```vue
<script setup lang="ts">
import { CircleIcon } from '@lucide/vue'

import { RadioGroupIndicator } from 'reka-ui'
import { inject } from 'vue'
import { radioAccordionItem } from './keys'

const item = inject(radioAccordionItem)
</script>

<template>
  <span data-slot="radio-accordion-indicator" :data-state="item?.checked.value ? 'checked' : 'unchecked'" class="fui-radio-accordion-indicator aspect-square">
    <RadioGroupIndicator class="flex items-center justify-center">
      <CircleIcon data-icon class="fill-current" />
    </RadioGroupIndicator>
  </span>
</template>
```

`components/ui/radio-accordion/RadioAccordionItem.vue`

```vue
<script lang="ts">
/** Groups one option with the details it reveals while selected. */
export default {}
</script>

<script setup lang="ts">
import type { AcceptableValue, PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { cva } from 'class-variance-authority'
import { injectRadioGroupRootContext, Primitive } from 'reka-ui'
import { computed, inject, provide, ref, toRef } from 'vue'
import { radioAccordionItem, radioAccordionVariant } from './keys'

const item = cva('fui-radio-accordion-item group/radio-accordion-item data-disabled:pointer-events-none data-disabled:fui-disabled', {
  variants: {
    variant: { plain: 'fui-radio-accordion-item-plain', outline: 'fui-radio-accordion-item-outline' },
  },
  defaultVariants: { variant: 'plain' },
})

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
      /** The value the group takes when this option is picked. */
      value: AcceptableValue
      /** Keeps the user from picking this option. */
      disabled?: boolean
    }
  >(),
  { as: 'div' },
)

const group = injectRadioGroupRootContext()
const variant = inject(radioAccordionVariant, ref('plain'))
const checked = computed(() => group.modelValue?.value === props.value)
const disabled = computed(() => props.disabled || group.disabled.value)

provide(radioAccordionItem, {
  value: toRef(() => props.value),
  disabled,
  checked,
})
</script>

<template>
  <Primitive
    :as="props.as"
    :as-child="props.asChild"
    data-slot="radio-accordion-item"
    :data-state="checked ? 'checked' : 'unchecked'"
    :data-disabled="disabled || undefined"
    :class="[item({ variant }), variant === 'plain' && 'last:border-b-0', props.class]"
  >
    <slot />
  </Primitive>
</template>
```

`components/ui/radio-accordion/RadioAccordionTrigger.vue`

```vue
<script lang="ts">
/** Labels its option and lets the user pick it. */
export default {}
</script>

<script setup lang="ts">
import type { RadioGroupItemProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { RadioGroupItem, useForwardProps } from 'reka-ui'
import { inject } from 'vue'
import { radioAccordionItem } from './keys'
import RadioAccordionIndicator from './RadioAccordionIndicator.vue'

const props = defineProps<Omit<RadioGroupItemProps, 'value' | 'disabled'> & { class?: HTMLAttributes['class'] }>()

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

const item = inject(radioAccordionItem)!
</script>

<template>
  <RadioGroupItem
    v-bind="forwarded"
    :value="item.value.value"
    :disabled="item.disabled.value"
    data-slot="radio-accordion-trigger"
    :class="['fui-radio-accordion-trigger cursor-pointer outline-none focus-visible:fui-focus-ring disabled:cursor-not-allowed', props.class]"
  >
    <RadioAccordionIndicator />
    <slot />
  </RadioGroupItem>
</template>
```

`components/ui/radio-accordion/keys.ts`

```ts
import type { AcceptableValue } from 'reka-ui'
import type { ComputedRef, InjectionKey, Ref } from 'vue'

export type RadioAccordionVariant = 'plain' | 'outline'

export const radioAccordionVariant: InjectionKey<Ref<RadioAccordionVariant>> = Symbol('radioAccordionVariant')

export interface RadioAccordionItemContext {
  value: Ref<AcceptableValue>
  disabled: Ref<boolean>
  checked: ComputedRef<boolean>
}

export const radioAccordionItem: InjectionKey<RadioAccordionItemContext> = Symbol('radioAccordionItem')
```

## API of the items the example uses

### Field

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

#### 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`. |

### Native Select

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

#### NativeSelect

Lets the user pick one option from a list using the browser's own picker. Renders a `<div>`.

`data-slot="native-select-wrapper"` · `NativeSelect.vue`

Styling: `native-select`, 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 |
| --- | --- | --- | --- |
| `modelValue` | `AcceptableValue \| AcceptableValue[]`: `null \| string \| number \| bigint \| Record<string, any> \| AcceptableValue[]` |  | The selected option'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 select. |

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

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

#### NativeSelectOptGroup

Groups related options of the native select under a shared heading. Renders a `<optgroup>`.

`data-slot="native-select-optgroup"` · `NativeSelectOptGroup.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` | `'optgroup'` | 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. |

#### NativeSelectOption

Offers one choice the user can pick in the native select. Renders a `<option>`.

`data-slot="native-select-option"` · `NativeSelectOption.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` | `'option'` | 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. |
