Examples

Without v-model

In case you don't want to modify your model directly, you can still use separate :input and @event bindings. This is especially useful if you are using data from external source, like Vuex, or transforming the data on each keystroke. In that case you have to manually take care of setting the $dirty by calling $touch() method.


<template>
  <input type="text" :value="name" @input="setName">
</template>
<script>
import useVuelidate from '@vuelidate/core'

export default {
  setup: () => ({ v$: useVuelidate() }),
  data: () => ({ name: '' }),
  validations () {
    return {
      name: { required }
    }
  },
  methods: {
    setName ($event) {
      // do some silly transformation
      this.name = $event.target.value.toUpperCase()
      this.v$.name.$touch()
    }
  }
}
</script>

Form submission

A common thing to do with forms, is to check their validity before submission. You can do this by calling the $validate method and check it's output.


<script>
export default {
  setup: () => ({ v$: useVuelidate() }),
  data: () => ({ name: '' }),
  validations () {
    return {
      name: { required }
    }
  },
  methods: {
    async submit () {
      const result = await this.v$.validate()
      if (!result) {
        // notify user form is invalid
        return
      }
      // perform async actions
    }
  }
}
</script>

Contextified validators

You can link fields together, by passing a field's value to another field's validator. An example of this being sameAs builtin validator.


<script>
import useVuelidate from '@vuelidate/core'
import { sameAs } from '@vuelidate/validators'

export default {
  setup: () => ({ v$: useVuelidate() }),
  data: () => ({ password: '', confirmPassword: '' }),
  validations () {
    return {
      password: { required },
      confirmPassword: sameAs(this.password)
    }
  },
}
</script>

Data nesting

You can nest validators to match your data as deep as you want. Parent validator is $invalid when any of its children validators reports an $invalid state. This might be very useful for overall form validation.


<script>
import useVuelidate from '@vuelidate/core'
import { sameAs } from '@vuelidate/validators'

export default {
  setup: () => ({ v$: useVuelidate() }),
  data: () => ({
    form: {
      address: {
        primary: ''
      }
    }
  }),
  validations () {
    return {
      form: {
        address: {
          primary: { required }
        }
      }
    }
  },
}
</script>

Collections validation

A parent element will collect all of its child component validations. This allows for easy validation of collections.


<template>
  <div>
    <input type="text" v-model="householdName">
    <PersonForm v-for="person in people" v-model="person" />
    <button @click.prevent="addPerson">Add person</button>
    <!-- this will contain all $errors from every <PersonForm/> component -->
    <p v-for="error of v.$errors" :key="error.$uid">
      {{ error.$message }}
    </p>
  </div>
</template>

<script>
import { useVuelidate } from '@vuelidate/core'
import PersonForm from '@/components/PersonForm'

export default {
  components: { PersonForm },
  data () {
    return {
      householdName: '',
      people: []
    }
  },
  validations () {
    return { householdName: { required } }
  },
  setup: () => ({ v: useVuelidate() }),
  methods: {
    addPerson () {
      this.people.push({
        name: '',
        email: '',
        address: ''
      })
    }
  }
}
</script>

Asynchronous validation

Async validators are just validators that return a Promise and are wrapped in the withAsync helper. Read more about it in Async Validators

import { helpers } from '@vuelidate/validators'

const isEmailTaken = (value) => fetch(`/api/unique/${value}`).then(r => r.json()) // check the email in the server

export default {
  validations () {
    return {
      email: {
        isUnique: helpers.withAsync(isEmailTaken)
      }
    }
  }
}

The async/await syntax is fully supported. It works especially great in combination with Fetch API.

export default {
  validations () {
    return {
      email: {
        async isUnique (value) {
          if (value === '') return true
          const response = await fetch(`/api/unique/${value}`)
          return (await response.json()).valid
        }
      }
    }
  }
}

Accessing validator parameters

You can access information about your validations through $params.


<template>
  <input type="text" v-model="name">
  <div>Note: Name must be at least {{ v$.name.$params.min }}</div>
</template>
<script>
import { minLength } from '@vuelidate/validators'
import useVuelidate from '@vuelidate/core'

export default {
  data: () => ({ name: '' }),
  validations () {
    return {
      name: {
        minLength: minLength(10)
      }
    }
  },
  setup: () => ({ v$: useVuelidate() })
}
</script>

Dynamic validation schema

Validations being a function or computed property, allow for a dynamic and changing schema, based on your model's data. Re-computation will happen automatically. Validation's $dirty state is preserved even if the property tree gets temporarily removed.

Options API


<script>
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'

export default {
  data: () => ({
    shippingAddress: '',
    billingSameAsShipping: false,
    billingAddress: ''
  }),
  validations () {
    const localRules = {
      shippingAddress: { required }
    }
    if (!this.billingSameAsShipping) {
      // if billing is not the same as shipping, require it
      localRules.billingAddress = {
        required
      }
    }
    return localRules
  },
  setup: () => ({ v$: useVuelidate() })
}
</script>

Composition API


<script>
import { reactive, computed } from 'vue'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'

export default {
  setup () {
    const state = reactive({
      shippingAddress: '',
      billingSameAsShipping: false,
      billingAddress: ''
    })
    const rules = computed(() => {
      const localRules = {
        shippingAddress: { required }
      }
      if (!state.billingSameAsShipping) {
        // if billing is not the same as shipping, require it
        localRules.billingAddress = {
          required
        }
      }
      return localRules
    })
    const v$ = useVuelidate(rules, state)
    return {
      v$
    }
  }
}
</script>

Dynamic parameters

Parameters passed to validators can be reactive, and be accessed from $params property.

const name = ref('')
const fieldLength = ref(5)

const rules = {
  name: { minLength: minLength(fieldLength) }
}

return {
  v$: useVuelidate(rules, { name }),
  fieldLength
}

Now everytime the fieldLength changes, the minLength validator will use the new value.


<template>
  <label>Name Length: <input type="text" v-model="fieldLength"></label>
  <label>Name: <input type="text" v-model="v$.name.$model"></label>
</template>

Returning metadata from validators

In cases where a validator can return more than just a boolean, we can return an object with a $valid boolean. All the extra data from what we return can be read through the $response property.

Let's implement a $warn concept, where a validator returns a warning, if the data is valid, but not ideal.


<template>
  <div>
    <DragAndDropArea v-model="v$.avatar.$model" />
    <div v-if="v$.name.imageSize.$response?.$warn" class="alert--warning">
      We recommend uploading an image at least {{ v$.name.imageSize.$response.recommendedSize }}kb,
      yours is {{ v$.name.imageSize.$response.currentSize }}kb.
    </div>
  </div>
</template>
<script>
import useVuelidate from '@vuelidate/core'

export default {
  setup () {
    const validateImage = (file) => {
      // perform some fancy FileReader logic, and return results
      return {
        $valid: isValid,
        $warn: isImageBigEnough,
        recommendedSize,
        currentSize
      }
    }
    const rules = {
      avatar: {
        imageSize: validateImage
      }
    }
    const avatar = ref(null)
    return {
      v$: useVuelidate(rules, { avatar }),
    }
  }
}
</script>

Reward early, punish late mode

The $rewardEarly mode of Vuelidate stops validators from running once a field becomes valid. You can re-run them using either $validate or $commit. This is often done on blur of the field.


<template>
  <div>
    <input v-model="v$.name.$model" @blur="v$.name.$commit" />
    <p v-for="error of v.$errors" :key="error.$uid">
      {{ error.$message }}
    </p>
  </div>
</template>
<script>
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'

export default {
  setup () {
    const rules = {
      name: { required }
    }
    const name = ref(null)
    return {
      v$: useVuelidate(rules, { name }, { $rewardEarly: true }),
    }
  }
}
</script>