---
name: form
title: Form
url: https://ui.frontic.com/components/ui/form.md
tier: ui
install: npx @frontic/ui add form
files:
  - components/ui/form/Form.vue
  - components/ui/form/index.ts
  - components/ui/form/schema.ts
registryDependencies:
  - field
dependencies:
  - "@standard-schema/spec"
  - "@vueuse/core"
  - reka-ui
---

# Form

Validates its fields on submit, against their own rules or one Standard Schema such as Zod 4, hands over their values, and shows server errors on the field they name.

## Install

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

Built from: [Field](https://ui.frontic.com/components/ui/field.md)

Used by: [Checkout Page](https://ui.frontic.com/components/templates/checkout-page.md)

npm packages: `@standard-schema/spec`, `@vueuse/core`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { ref, useId } from 'vue'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldTitle } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Form } from '@/components/ui/form'

const shipping = z.object({
  name: z.string().trim().min(1, 'Enter the name for the parcel.'),
  email: z.email('Enter a full email address, like you@example.com.'),
  postcode: z.string().regex(/^\d{5}$/, 'Enter a five-digit postcode.'),
  delivery: z.enum(['standard', 'express'], 'Choose how we deliver the parcel.'),
})

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

const errors = ref<Record<string, string>>({})
const sending = ref(false)

/** Stands in for the shop's API, which knows where Basecamp delivers. */
async function submit(values: Record<string, unknown>) {
  sending.value = true
  await new Promise((resolve) => setTimeout(resolve, 400))
  errors.value = values.postcode === '00000' ? { postcode: "We don't ship to this postcode yet." } : {}
  sending.value = false
}
</script>

<template>
  <Form :schema="shipping" :errors="errors" @form-submit="submit">
    <FieldGroup>
      <Field name="name">
        <FieldLabel>Full name</FieldLabel>
        <Input autocomplete="name" />
        <FieldError />
      </Field>
      <Field name="email">
        <FieldLabel>Email</FieldLabel>
        <Input type="email" autocomplete="email" />
        <FieldDescription>We send your order confirmation here.</FieldDescription>
        <FieldError />
      </Field>
      <Field name="postcode">
        <FieldLabel>Postcode</FieldLabel>
        <Input inputmode="numeric" autocomplete="postal-code" />
        <FieldError />
      </Field>
      <Field name="delivery">
        <FieldLabel :native-label="false">Delivery</FieldLabel>
        <RadioGroup>
          <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>
        <FieldError />
      </Field>
    </FieldGroup>
    <Button type="submit" :disabled="sending">Continue to payment</Button>
  </Form>
</template>
```

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

## API reference

### Form

Validates its fields on submit, hands over their values, and shows server errors on the field they name. Renders a `<form>`.

`data-slot="form"` · `Form.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `errors` | `Record<string, string \| string[]>` |  | A map of field `name` to server-side error message(s). Displayed by the matching `FieldRoot`'s `FieldError` until the user edits that field, or a new value for that key is provided. |
| `validationMode` | `FieldValidationMode`: `'onBlur' \| 'onChange' \| 'onSubmit'` | `'onSubmit'` | When fields validate. The `validationMode` of a `FieldRoot` takes precedence over this.<br>- `onSubmit`: when the form is submitted, then on every change after that.<br>- `onBlur`: when a control loses focus.<br>- `onChange`: on every change to a control's value. |
| `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` | `'form'` | 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. |
| `schema` | `StandardSchema` |  | Validates every named field without a `validate` of its own against the form's values; any Standard Schema, such as Zod 4. Values arrive as the inputs' strings. A field shows the issues at its name or inside it; any other issue holds the submit back and reaches the default slot. |

| Event | Payload | Description |
| --- | --- | --- |
| `submit` | `event: SubmitEvent` | Emitted with the native submit event once every field has validated successfully. When any field is invalid, the native submission is prevented and this isn't emitted. Otherwise the form submits natively (e.g. to its `action`) unless you call `event.preventDefault()`, or listen to `formSubmit`. |
| `formSubmit` | `values: Record<string, unknown>, event: SubmitEvent` | Emitted after `submit` with the values of every named field. Listening to it prevents the native submission. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ errors: string[]; }` | The fields and the submit button; gets the schema's messages that no field shows as `errors`. |

## Exports

```ts
import { type StandardSchema, type StandardSchemaIssue } from '@/components/ui/form'
```

- `StandardSchema`: `StandardSchemaV1`
- `StandardSchemaIssue`: `StandardSchemaV1.Issue`

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`form`](https://ui.frontic.com/DESIGN.md#form) |

## Source

`components/ui/form/Form.vue`

```vue
<script lang="ts">
/** Validates its fields on submit, hands over their values, and shows server errors on the field they name. */
export default {}
</script>

<script setup lang="ts">
import type { FormRootEmits, FormRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { StandardSchema } from './schema'
import { reactiveOmit } from '@vueuse/core'
import { FormRoot, useForwardProps } from 'reka-ui'
import { computed, getCurrentInstance, provide, ref, useTemplateRef } from 'vue'
import { fronticFieldValidate } from '@library/components/ui/field'
import { schemaValidate, unclaimed } from './schema'

const props = defineProps<
  FormRootProps & {
    class?: HTMLAttributes['class']
    /** Validates every named field without a `validate` of its own against the form's values; any Standard Schema, such as Zod 4. Values arrive as the inputs' strings. A field shows the issues at its name or inside it; any other issue holds the submit back and reaches the default slot. */
    schema?: StandardSchema
  }
>()

const emit = defineEmits<FormRootEmits>()

defineSlots<{
  /** The fields and the submit button; gets the schema's messages that no field shows as `errors`. */
  default?: (props: { errors: string[] }) => unknown
}>()

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

/** Reka keeps the browser from submitting whenever a formSubmit listener is bound: bind it when the consumer did, or when a schema has to check the whole form first. */
const listensToFormSubmit = Boolean(getCurrentInstance()?.vnode.props?.onFormSubmit)
/** Submits whose default the consumer's own submit handler prevented: they handle the submit, so it never goes out natively. */
const handled = new WeakSet<Event>()
const listeners = computed(() => ({
  onSubmit: (event: SubmitEvent) => {
    emit('submit', event)
    if (event.defaultPrevented) handled.add(event)
  },
  ...(listensToFormSubmit || props.schema ? { onFormSubmit } : {}),
}))

const errors = ref<string[]>([])
async function onFormSubmit(values: Record<string, unknown>, event: SubmitEvent) {
  errors.value = props.schema ? await unclaimed(props.schema, values) : []
  if (errors.value.length) return
  if (listensToFormSubmit) emit('formSubmit', values, event)
  else if (!handled.has(event)) send(event)
}

const SUBMITTER_OVERRIDES = [
  ['formaction', 'action'],
  ['formmethod', 'method'],
  ['formenctype', 'enctype'],
  ['formtarget', 'target'],
] as const

/** Submits the form the browser was kept from submitting, with the button that sent it and the form attributes that button overrides. */
function send(event: SubmitEvent) {
  const form = event.target as HTMLFormElement
  const submitter = event.submitter as HTMLButtonElement | HTMLInputElement | null
  const carried = submitter?.name ? Object.assign(document.createElement('input'), { type: 'hidden', name: submitter.name, value: submitter.value }) : undefined
  if (carried) form.append(carried)
  const restore = SUBMITTER_OVERRIDES.flatMap(([own, attribute]) => {
    const value = submitter?.getAttribute(own)
    if (value == null) return []
    const before = form.getAttribute(attribute)
    form.setAttribute(attribute, value)
    return [() => (before === null ? form.removeAttribute(attribute) : form.setAttribute(attribute, before))]
  })
  form.submit()
  carried?.remove()
  for (const undo of restore) undo()
}

provide(fronticFieldValidate, (name) => (props.schema ? schemaValidate(props.schema, name) : undefined))

const root = useTemplateRef<{ validate: (name?: string) => boolean }>('root')

defineExpose({
  /** Validates every field, or the fields with this name; true when all are valid. */
  validate: (name?: string) => root.value?.validate(name) ?? false,
})
</script>

<template>
  <FormRoot ref="root" v-bind="{ ...forwarded, ...listeners }" :class="['fui-form', props.class]" data-slot="form">
    <slot :errors="errors" />
  </FormRoot>
</template>
```

`components/ui/form/schema.ts`

```ts
import type { StandardSchemaV1 } from '@standard-schema/spec'
import type { FieldValidateFn, FieldValidateResult } from 'reka-ui'

/** A schema in the Standard Schema shape: Zod 4, Valibot and ArkType all are. */
export type StandardSchema = StandardSchemaV1
/** One problem a schema reports, at the path of the value it is about. */
export type StandardSchemaIssue = StandardSchemaV1.Issue

/** The form's values with dotted names as nested objects, so `address.city` meets a nested schema. */
function nested(values: Record<string, unknown>) {
  const root: Record<string, unknown> = {}
  for (const [name, value] of Object.entries(values)) {
    const keys = name.split('.')
    let node = root
    for (const key of keys.slice(0, -1)) node = (node[key] ??= {}) as Record<string, unknown>
    node[keys.at(-1)!] = value
  }
  return root
}

const dotted = (issue: StandardSchemaIssue) => (issue.path ?? []).map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')

/** A field shows an issue whose path is its name, `address.city`, or lies inside it. */
const claims = (name: string, issue: StandardSchemaIssue) => {
  const path = dotted(issue)
  return path === name || path.startsWith(`${name}.`)
}

const messagesFor = (name: string, issues: ReadonlyArray<StandardSchemaIssue> = []): FieldValidateResult => issues.filter((issue) => claims(name, issue)).map((issue) => issue.message)

/** A Field validator from a form schema: runs the schema on the form's values and keeps the messages for this field's name. Stays sync for a sync schema, so a submit sees the result. */
export function schemaValidate(schema: StandardSchema, name: string): FieldValidateFn {
  return (_value, formValues) => {
    const result = schema['~standard'].validate(nested(formValues))
    return 'then' in result ? result.then(({ issues }) => messagesFor(name, issues)) : messagesFor(name, result.issues)
  }
}

/** The messages of the issues no field of the form's values claims: a check on the whole object, or a key without a field. */
export async function unclaimed(schema: StandardSchema, values: Record<string, unknown>): Promise<string[]> {
  const { issues = [] } = await schema['~standard'].validate(nested(values))
  const names = Object.keys(values)
  return issues.filter((issue) => !names.some((name) => claims(name, issue))).map((issue) => issue.message)
}
```

## API of the items the example uses

### Button

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

#### Button

Lets the user trigger an action, such as adding a product to the cart. Renders a `<button>`.

`data-slot="button"` · `Button.vue`

Styling: `button`, which extends [`action`](https://ui.frontic.com/DESIGN.md#action). 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` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'solid'` | The visual style: a filled, outlined, tinted, background-free or text-link button. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'primary'` | The colour role, e.g. primary for the brand colour, buy for add-to-cart, checkout for the checkout step, destructive for removing. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's height and padding; the `icon-*` sizes make a square, icon-only button. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |

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

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