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>@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/onChangetiming and optional debounce. - Validates
requiredon 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-focusedon 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
yarn add akar
npm install akar
bun 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.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
dirty | booleanWhether the field's value has changed from its initial value. Useful when the field state is controlled by an external library. | |
disabled | booleanWhen | |
invalid | booleanWhen | |
name | stringThe name of the field. Submitted with its owning form as part of a
name/value pair, and used to match server | |
required | booleanWhen | |
touched | booleanWhether the field has been touched (its control blurred). Useful when the field state is controlled by an external library. | |
validate | FieldValidateFnCustom validation function. Return an error message (or array of
messages) when invalid, or Can be async, but an async | |
validationDebounceTime | numberHow long to wait (in ms) between | |
validationMode | "onSubmit" | "onBlur" | "onChange" | "onSubmit"When the field (re-)runs validation. Takes precedence over the
|
| Slot (default) | Payload |
|---|---|
invalid | booleanWhether the field is currently invalid. |
errors | string[]All current error messages. |
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>
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
for | stringThe id of the element the label is associated with. | |
nativeLabel | true | booleanWhether the label renders a native Useful for button controls like |
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.
| Prop | Default | Type |
|---|---|---|
as | "input" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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 | numberThe value of the control when it is initially rendered. Use when you do not need to control its value. | |
id | stringId of the element. Falls back to the id generated by the ancestor | |
modelValue | string | numberThe controlled value of the control. Can be bound with |
| Event | Payload |
|---|---|
update:modelValue | value: stringEvent handler called when the value of the control changes. |
Description
A description for the field, automatically added to the control's aria-describedby. Renders a p by default.
| Prop | Default | Type |
|---|---|---|
as | "p" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
id | stringId of the element. Auto-generated when not provided. |
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 customvalidatemessage, or the browser's validation message. Several messages render as a list. - With a
ValidityStatekey likematch="valueMissing", it shows only for that constraint, typically with your own message. - With
matchset totrue, 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.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
id | stringId of the element. Auto-generated when not provided. | |
match | boolean | keyof ValidityStateRestricts when this error renders:
|
| Slot (default) | Payload |
|---|---|
errors | string[]The error messages this part displays. |
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 | FieldValidityStateThe field's validity. |
errors | string[]All current error messages. |
error | stringThe first error message, or an empty string. |
value | unknownThe value validity was last computed for. |
initialValue | unknownThe control's initial value. |
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 owningFormRootsubmits, then on every change after that.onBlur: when the control loses focus.onChange: on every change to the control's value. Usevalidation-debounce-timeto wait betweenvalidatecalls.
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.