Field

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

We'll never share your email.

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

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.

pnpm add akar

Anatomy

Import all parts and piece them together.

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

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

Read our Composition guide for more details.

dirty
boolean

Whether the field's value has changed from its initial value. 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. 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 name/value pair, and used to match server errors on a FormRoot. 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). Useful when the field state is controlled by an external library.

validate
FieldValidateFn

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.

Can be async, but an async validate does not prevent form submission 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 validationMode of an ancestor FormRoot.

  • onSubmit: when the form is submitted, then on every change after that.
  • onBlur: when the control loses focus.
  • onChange: on every change to the control's value.
Slot (default)Payload
invalid
boolean

Whether the field is currently invalid.

errors
string[]

All current error messages.

FieldRoot.vue source

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

<FieldLabel as="div" :native-label="false">
  Fruit
</FieldLabel>
PropDefaultType
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.

Read our 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 rendering another element (e.g. as="div"): it then drops for, focuses the control on click, and relies on the control's aria-labelledby.

Useful for button controls like SelectTrigger, where a native label would forward clicks and :hover to the button.

FieldLabel.vue source

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.

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

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

EventPayload
update:modelValue
value: string

Event handler called when the value of the control changes.

FieldControl.vue source

Description

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

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

Read our Composition guide for more details.

id
string

Id of the element. Auto-generated when not provided.

FieldDescription.vue source

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.

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

Read our 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:

  • a ValidityState key (e.g. "valueMissing") — renders when that constraint fails.
  • true — always renders, letting an external library control visibility.
  • omitted — renders whenever the field is invalid.
Slot (default)Payload
errors
string[]

The error messages this part displays.

FieldError.vue source

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.

<FieldValidity v-slot="{ validity, error }">
  <p v-if="validity.valid === false">
    {{ error }}
  </p>
</FieldValidity>
Slot (default)Payload
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

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.

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

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

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