21

Form state

Read reactive form and field state such as dirty, touched, valid, validating, and submitting, and avoid the common pitfalls.

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

PropertyMeaning
isDirtyCurrent values differ (deep comparison) from the baseline initialValues. Reverting every change makes it false again.
isTouchedA field has blurred, or a submit has been attempted.
isSubmittedAt least one submit has been attempted, even an invalid one.
isSubmittingValidation or the asynchronous onSubmit is running.
isValidatingA schema validation is running.
isValidThe current error list is empty. It does not run validation.
submitCountNumber of submit attempts. Reset to 0 by reset().
resetCountNumber of resets. Useful as a :key.
errorsThe current list of issues.
valuesThe 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 reports true, so the button stays enabled and signals nothing. Submitting is already safe, because onSubmit only 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.

PropertyMeaning
errorsIssues reported at exactly this field's path.
isValiderrors is empty.
isDirtyThe value differs (deep comparison) from the baseline value at this path.
isTouchedThe field blurred, or a submit was attempted.
isValidatingThe 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:

  • isDirty compares the whole array, so editing a nested item makes the array field dirty.
  • isValidating includes validation of fields nested in its items.
  • errors and isValid refer only to issues at the array's own path, such as a minimum item count. They do not include item errors.
  • isTouched becomes true after a submit, or when the array field's own onBlur fires. 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>

Next steps

  • useForm lists the complete public contract.
  • Field documents the field members.
Copyright © 2026 NotForm
Published under MIT License