Form state
State is exposed as plain reactive properties. Read only what your interface needs. Vue tracks those reads in templates and computed values, so there is nothing to subscribe to.
Form state
| Property | Meaning |
|---|---|
isDirty | Current values differ (deep comparison) from the baseline initialValues. Reverting every change makes it false again. |
isTouched | A field has blurred, or a submit has been attempted. |
isSubmitted | At least one submit has been attempted, even an invalid one. |
isSubmitting | Validation or the asynchronous onSubmit is running. |
isValidating | A schema validation is running. |
isValid | The current error list is empty. It does not run validation. |
submitCount | Number of submit attempts. Reset to 0 by reset(). |
resetCount | Number of resets. Useful as a :key. |
errors | The current list of issues. |
values | The live input values. |
<template>
<Form :form="form">
<p v-if="form.isDirty">
You have unsaved changes.
</p>
<p
v-if="form.isValidating"
role="status"
>
Checking values...
</p>
<button
type="submit"
:disabled="form.isSubmitting"
>
{{ form.isSubmitting ? 'Saving...' : 'Save' }}
</button>
</Form>
</template>
isValid describes errors, not values
isValid is true whenever form.errors is empty. A pristine form with invalid values reports isValid === true until something validates. Two consequences:
- Do not use
:disabled="!form.isValid"to gate submission. A pristine but invalid form reportstrue, so the button stays enabled and signals nothing. Submitting is already safe, becauseonSubmitonly runs when the schema passes. - To get a fresh answer, call
await form.validate(), or submit.
Prefer disabling the button with isSubmitting, and let validation show errors after the user acts.
Field state
Every field instance exposes its own state.
| Property | Meaning |
|---|---|
errors | Issues reported at exactly this field's path. |
isValid | errors is empty. |
isDirty | The value differs (deep comparison) from the baseline value at this path. |
isTouched | The field blurred, or a submit was attempted. |
isValidating | The whole form, this field, or a field nested inside it is being validated. |
<template>
<Field
v-slot="{ field }"
:field="form.field('username')"
>
<label :for="field.path">Username</label>
<input
v-bind="field.events"
:id="field.path"
v-model="field.model"
type="text"
autocomplete="username"
>
<span v-if="field.isValidating">Checking...</span>
<FieldError :errors="field" />
</Field>
</template>
Array fields and nested paths
An array field has the same members, with these specifics:
isDirtycompares the whole array, so editing a nested item makes the array field dirty.isValidatingincludes validation of fields nested in its items.errorsandisValidrefer only to issues at the array's own path, such as a minimum item count. They do not include item errors.isTouchedbecomestrueafter a submit, or when the array field's ownonBlurfires. It is not derived from item fields.
To ask whether any item has an error, check the issue list, as described in Errors.
Common patterns
Unsaved changes warning
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave(() => {
if (form.isDirty && !confirm('Discard unsaved changes?')) {
return false
}
})
Disable Save until something changed
<template>
<button
type="submit"
:disabled="!form.isDirty || form.isSubmitting"
>
Save
</button>
</template>
Show errors only after interaction
Fields do not validate until the user acts, so errors already appear at the right time. If you add your own early validation, for example with validateOn: { input: true }, you can still gate the display on touch:
<template>
<FieldError
v-if="field.isTouched"
:errors="field"
/>
</template>