Errors
All errors are issues: an object with a message and an optional path.
{ message: 'Enter a valid email address.', path: ['email'] }
pathis an array of segments. Segments are property names and array indices:['contacts', 0, 'email']. Depending on the validator, a segment may be a string, a number, or an object with akey.- An issue without a path is a form-level issue.
form.errors is the full list. Each field exposes the issues at its own exact path as field.errors.
Show a field's error
FieldError renders the first message and nothing when there is no error.
<template>
<Field
v-slot="{ field }"
:field="form.field('email')"
>
<label :for="field.path">Email</label>
<input
v-bind="field.events"
:id="field.path"
v-model="field.model"
type="email"
>
<FieldError :errors="field" />
</Field>
</template>
Style it, change its element, or use a slot. See FieldError.
<template>
<FieldError
:errors="field"
as="p"
class="text-red-600"
role="alert"
/>
</template>
Show every error
FieldError shows only the first message. To list all of them, render field.errors.
<template>
<ul v-if="field.errors.length > 0">
<li
v-for="(issue, index) in field.errors"
:key="index"
>
{{ issue.message }}
</li>
</ul>
</template>
Show form-level errors
Read form.errors and keep the issues without a path.
<script setup lang="ts">
import { computed } from 'vue'
const formErrors = computed(() => {
return form.errors.filter((issue) => {
return !issue.path?.length
})
})
</script>
<template>
<p
v-for="(issue, index) in formErrors"
:key="index"
role="alert"
>
{{ issue.message }}
</p>
</template>
You can also pass an issue array straight to FieldError: <FieldError :errors="formErrors" as="p" role="alert" />.
Show errors for a section
A field shows only issues at its exact path. An issue on ['address'] does not appear on the address.city field. To summarize a whole section, filter by the first segment.
function keyOf(segment: PropertyKey | { key: PropertyKey }) {
return typeof segment === 'object' ? segment.key : segment
}
function sectionErrors(section: string) {
return form.errors.filter((issue) => {
return issue.path?.[0] !== undefined && String(keyOf(issue.path[0])) === section
})
}
Show an error summary
After a failed submit, form.errors holds every problem in the form. It is the natural source for a summary at the top of a long form.
<template>
<div
v-if="form.isSubmitted && form.errors.length > 0"
role="alert"
>
<p>Fix {{ form.errors.length }} problem(s) to continue.</p>
<ul>
<li
v-for="(issue, index) in form.errors"
:key="index"
>
{{ issue.message }}
</li>
</ul>
</div>
</template>
Add errors yourself
form.setError(issue) adds an error, typically after a server response.
form.setError({ message: 'This email address is already in use.', path: ['email'] })
form.setError({ message: 'We could not reach the server.' }) // form-level
- With a
path, it replaces the errors already at that exact path. - Without a
path, it appends a form-level error. Calling it twice adds two.
See Server errors for a complete submit flow.
Start with errors
Pass initialErrors when a form should open with issues, for example after a server-side render.
const form = useForm({
initialErrors: [{ message: 'Verify this address.', path: ['email'] }],
initialValues: { email: 'jane@example.com' },
schema,
})
reset() returns to the current initial errors. reset(values, errors) replaces them.
When errors change
| Event | Effect on errors |
|---|---|
A field validates (blur, change, input, field.validate()) | Replaces only the errors at that field's exact path. |
form.validate() | Replaces all errors with the schema's result. |
| Submit | Replaces all errors with the schema's result. A passing submit clears them before onSubmit runs. |
form.setError(issue) | Adds or replaces errors at one path, or appends a form-level error. |
| An array mutation | Errors stay attached to the item they belong to. Removed items lose their errors. See Array fields. |
form.reset() | Restores the initial errors. |
email disappears the next time email validates and the schema reports no problem there. Form-level server errors stay until the next submit or reset. This is usually what you want, since editing a field makes its server error stale.Accessible error markup
Connect each message to its control so assistive technology announces it.
<template>
<Field
v-slot="{ field }"
:field="form.field('email')"
>
<label :for="field.path">Email</label>
<input
:id="field.path"
v-model="field.model"
v-bind="field.events"
type="email"
:aria-invalid="!field.isValid"
:aria-describedby="field.isValid ? undefined : `${field.path}-error`"
>
<FieldError
:id="`${field.path}-error`"
:errors="field"
role="alert"
/>
</Field>
</template>
FieldError forwards id, class, role, and other attributes to the element it renders.