---
title: "Field"
description: "Accessible wiring between a label, control, description, and error message, with built-in validation."
canonical_url: "https://vinicunca.dev/akar/components/field"
---
# Field

> Accessible wiring between a label, control, description, and error message, with built-in validation.

```vue [index.vue]
<script setup lang="ts">
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from 'akar'
import './styles.css'
</script>

<template>
  <div class="akar-demo akar-demo-Field">
    <FieldRoot
        class="FieldRoot"
        name="email"
        validation-mode="onBlur"
      >
        <FieldLabel class="FieldLabel">
          Email
        </FieldLabel>
        <FieldControl
          type="email"
          required
          placeholder="you@example.com"
          class="FieldControl"
        />
        <FieldDescription class="FieldDescription">
          We'll never share your email.
        </FieldDescription>
        <FieldError
          match="valueMissing"
          class="FieldError"
        >
          Email is required.
        </FieldError>
        <FieldError
          match="typeMismatch"
          class="FieldError"
        >
          Please enter a valid email address.
        </FieldError>
      </FieldRoot>
  </div>
</template>
```

```css [styles.css]
@scope (.akar-demo-Field) {
.FieldRoot {
  display: flex;
  width: 256px;
  flex-direction: column;
  gap: 6px;
}

.FieldLabel {
  font-size: 14px;
  font-weight: 500;
  color: white;
}

.FieldControl {
  border-radius: 6px;
  border: 1px solid #404040;
  background-color: #171717;
  padding: 8px 12px;
  font-size: 14px;
  color: white;
  outline: none;
}

.FieldControl:focus {
  box-shadow: 0 0 0 2px #10b981;
}

.FieldControl[data-invalid] {
  border-color: #ef4444;
}

.FieldDescription {
  font-size: 12px;
  color: #a3a3a3;
}

.FieldError {
  font-size: 12px;
  color: #ef4444;
}
}
```

## Features

- Generates and wires up ids/aria-describedby/aria-invalid automatically.
- Works with a plain native input, or with existing reka controls (Checkbox, Select, DateField, ...) placed inside it.
- Sync or async custom validation, with `onSubmit`/`onBlur`/`onChange` timing and optional debounce.
- Validates `required` on non-native controls (Checkbox, Select, DateField) too.
- Reads native constraint-validation state (`required`, `pattern`, `type=email`, ...) automatically.
- Exposes `data-valid`/`data-invalid`/`data-dirty`/`data-touched`/`data-filled`/`data-focused` on every part for styling.
- Shows the browser's validation message by default, or your own per failing constraint.
- Fully additive — every reka control behaves exactly as before when used outside a Field.

## Installation

Install the component from your command line.

```bash [pnpm]
pnpm add akar
```

```bash [yarn]
yarn add akar
```

```bash [npm]
npm install akar
```

```bash [bun]
bun add akar
```

## Anatomy

Import all parts and piece them together.

```vue
<script setup>
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot, FieldValidity } from 'akar';
</script>

<template>
  <FieldRoot>
    <FieldLabel />
    <FieldControl />
    <FieldDescription />
    <FieldError />
    <FieldValidity />
  </FieldRoot>
</template>
```

## API Reference

### Root

Contains all the parts of a field, provides shared ids, and runs validation. Renders a `div` by default.

Exposes a `validate()` method through a template ref, which validates the field regardless of `validationMode` and returns whether it's valid. A disabled field skips validation.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `dirty` |  | `boolean` | Whether the field's value has changed from its initial value.<br>Useful when the field state is controlled by an external library. |
| `disabled` |  | `boolean` | When `true`, prevents the user from interacting with the field's control. Takes precedence over the `disabled` of the control. |
| `invalid` |  | `boolean` | When `true`, marks the field invalid regardless of its own validation.<br>Useful when the field state is controlled by an external library. |
| `name` |  | `string` | The name of the field. Submitted with its owning form as part of a<br>name/value pair, and used to match server `errors` on a `FormRoot`.<br>Takes precedence over the `name` of the control. |
| `required` |  | `boolean` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `touched` |  | `boolean` | Whether the field has been touched (its control blurred).<br>Useful when the field state is controlled by an external library. |
| `validate` |  | `FieldValidateFn` | Custom validation function. Return an error message (or array of<br>messages) when invalid, or `null`/`undefined` when valid. Receives the<br>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<br>when `validationMode` is `onSubmit`. |
| `validationDebounceTime` |  | `number` | How long to wait (in ms) between `validate` calls when validating on change. |
| `validationMode` | `"onSubmit"` | `"onBlur" \| "onChange" \| "onSubmit"` | When the field (re-)runs validation. Takes precedence over the<br>`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. |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `invalid` | `boolean` | Whether the field is currently invalid. |
| `errors` | `string[]` | All current error messages. |

[FieldRoot.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field/FieldRoot.vue)

Every part (and a participating reka control) renders the same data attributes, so any of them can be styled by the field's state.

| Data attribute | Value |
| --- | --- |
| `[data-valid]` | Present once validation has run and the field is valid |
| `[data-invalid]` | Present once validation has run and the field is invalid |
| `[data-dirty]` | Present while the control's value differs from its initial value |
| `[data-touched]` | Present once the control has been blurred |
| `[data-filled]` | Present when the control has a non-empty value |
| `[data-focused]` | Present while the control is focused |
| `[data-disabled]` | Present when disabled |

### Label

Renders a [Label](https://vinicunca.dev/akar/components/label), automatically wired to the control via `for`/`id` and the control's `aria-labelledby`.

For a button control like `SelectTrigger`, a native `label` forwards clicks and `:hover` to the button. Render another element with `:native-label="false"` instead: it drops `for` and focuses the control on click.

```vue
<FieldLabel as="div" :native-label="false">
  Fruit
</FieldLabel>
```

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `for` |  | `string` | The id of the element the label is associated with. |
| `nativeLabel` | `true` | `boolean` | Whether the label renders a native `label` element. Set to `false` when<br>rendering another element (e.g. `as="div"`): it then drops `for`, focuses<br>the control on click, and relies on the control's `aria-labelledby`.<br><br>Useful for button controls like `SelectTrigger`, where a native label<br>would forward clicks and `:hover` to the button. |

[FieldLabel.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field/FieldLabel.vue)

### Control

A native form control (`input` by default — pass `as="textarea"` or `as="select"` for others). Binds `id`/`name`/`disabled`/`required`/`aria-labelledby`/`aria-describedby`/`aria-invalid` from the Field, and reports focus/blur/input interactions back to it. The Field's `name` takes precedence over the control's; `disabled` and `required` apply when set on either.

Bind its value with `v-model`, or set a starting value with `default-value`. A controlled value is validated when it changes, so a value you reject or rewrite never reaches the field.

Existing reka controls (`CheckboxRoot`, `SelectRoot`/`SelectTrigger`, `DateFieldRoot`, ...) can be used directly inside a `FieldRoot` instead of `FieldControl` — they pick up the same wiring automatically by optionally reading the Field's context, and behave exactly as before when used outside a Field.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"input"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `defaultValue` |  | `string \| number` | The value of the control when it is initially rendered. Use when you do not need to control its value. |
| `id` |  | `string` | Id of the element. Falls back to the id generated by the ancestor `FieldRoot`. |
| `modelValue` |  | `string \| number` | The controlled value of the control. Can be bound with `v-model`. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `value: string` | Event handler called when the value of the control changes. |

[FieldControl.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field/FieldControl.vue)

### Description

A description for the field, automatically added to the control's `aria-describedby`. Renders a `p` by default.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"p"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `id` |  | `string` | Id of the element. Auto-generated when not provided. |

[FieldDescription.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field/FieldDescription.vue)

### Error

An error message for the field, added to the control's `aria-describedby` while shown. Renders a `div` by default.

- Without `match`, it shows whenever the field is invalid. Its default content is the current message: a server error, a custom `validate` message, or the browser's validation message. Several messages render as a list.
- With a `ValidityState` key like `match="valueMissing"`, it shows only for that constraint, typically with your own message.
- With `match` set to `true`, it always shows, letting an external library control it.

It never shows while the field is disabled. Its `data-state` is `open` or `closed`, and it waits for a closing animation before unmounting, keeping its last message meanwhile.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `forceMount` |  | `boolean` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `id` |  | `string` | Id of the element. Auto-generated when not provided. |
| `match` |  | `boolean \| keyof ValidityState` | 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. |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `errors` | `string[]` | The error messages this part displays. |

[FieldError.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field/FieldError.vue)

### Validity

Renders no element. Passes the field's `validity`, `errors`, `error`, `value` and `initialValue` to its slot, for displaying something custom based on the field's validity.

```vue
<FieldValidity v-slot="{ validity, error }">
  <p v-if="validity.valid === false">
    {{ error }}
  </p>
</FieldValidity>
```

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `validity` | `FieldValidityState` | The field's validity. `valid` is `null` until the field has a validity to report. |
| `errors` | `string[]` | All current error messages. |
| `error` | `string` | The first error message, or an empty string. |
| `value` | `unknown` | The value validity was last computed for. |
| `initialValue` | `unknown` | The control's initial value. |

[FieldValidity.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field/FieldValidity.vue)

## Examples

### Custom validation

Pass a sync or async `validate` function. It receives the control's value and, inside a `FormRoot`, the values of every named field. Return a message (or array of messages) when invalid, or `null`/`undefined` when valid. An async `validate` doesn't prevent form submission in `onSubmit` mode.

```vue
<script setup>
import { FieldControl, FieldError, FieldLabel, FieldRoot } from 'akar'

async function checkUsername(value) {
  const isTaken = await api.isUsernameTaken(value)
  return isTaken ? 'That username is already taken.' : null
}
</script>

<template>
  <FieldRoot name="username" :validate="checkUsername" validation-mode="onBlur">
    <FieldLabel>Username</FieldLabel>
    <FieldControl />
    <FieldError v-slot="{ errors }">
      {{ errors[0] }}
    </FieldError>
  </FieldRoot>
</template>
```

### Validation timing

Use `validation-mode` to control when validation (native constraint checks and the custom `validate` function) runs. Whatever the mode, an empty `required` field isn't reported until the user has changed its value, or the form is submitted:

- `onSubmit` (default): when the owning `FormRoot` submits, then on every change after that.
- `onBlur`: when the control loses focus.
- `onChange`: on every change to the control's value. Use `validation-debounce-time` to wait between `validate` calls.

A `FieldRoot` without its own `validation-mode` uses the `FormRoot`'s.

```vue
<template>
  <FieldRoot
    name="email"
    validation-mode="onChange"
    :validation-debounce-time="300"
  >
    <!-- ... -->
  </FieldRoot>
</template>
```

### Using an existing reka control

Place any reka form control inside a `FieldRoot` instead of `FieldControl` — it participates automatically.

```vue
<script setup>
import { FieldDescription, FieldLabel, FieldRoot, SelectContent, SelectItem, SelectPortal, SelectRoot, SelectTrigger, SelectValue, SelectViewport } from 'akar'
</script>

<template>
  <FieldRoot name="fruit" required>
    <FieldLabel>Fruit</FieldLabel>
    <SelectRoot>
      <SelectTrigger>
        <SelectValue placeholder="Select a fruit" />
      </SelectTrigger>
      <SelectPortal>
        <SelectContent>
          <SelectViewport>
            <SelectItem value="apple">
              Apple
            </SelectItem>
          </SelectViewport>
        </SelectContent>
      </SelectPortal>
    </SelectRoot>
    <FieldDescription>Pick your favorite.</FieldDescription>
  </FieldRoot>
</template>
```

Participating controls report focus/dirty/filled state, pass their actual value to `validate`, and enforce `required`: an empty `Select`, an unchecked `Checkbox` or an empty `DateField` makes the field invalid (`match="valueMissing"`).

## Accessibility

`FieldLabel` associates with the control via matching `for`/`id` and `aria-labelledby`. `FieldDescription` and any shown `FieldError` are added to the control's `aria-describedby`, and `aria-invalid` is set once the field is known to be invalid. Custom errors are also set on native controls with `setCustomValidity`, so the browser's own validity state agrees with the field.

---

- [GitHub](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Field)
- [npm](https://www.npmjs.com/package/akar)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
