Vue 3 Form Validation Example With Explanation

Vue 3 Form Validation Example

Let’s see How to validate form inputs in Vue.js 3 With this Vue 3 form validation example that covers the most commonly used inputs.

We will create Vue.js single-page component for a registration form and set validation rules and error messages. I’m using bootstrap 5 as a CSS framework but it’s okay to work with other frameworks as we only focus on Vue code.

Useful tutorials

Here is the Template part of the example before the validation. Just a registration form that takes the first name, last name, email, age, start date, end date, password, and password confirmation.

Vue 3 Form Validation Example
Vue 3 Form error validation
<template>
<section class="h-100 h-custom bg-light" >
    <div class="container py-5 h-100">
        <div class="row d-flex justify-content-center align-items-center h-100">
            <div class="col d-flex justify-content-center align-items-center">
                    
                <div class="card border-0 " style="min-width:350px; max-width:500px">
                    <div class="card-body">
                        <h3 class="mb-4">Sign up</h3>
                        <hr/>
                        <form @submit="onSubmit">
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">First name</label>
                                <input type="text" class="form-control"
                                    v-model.trim="form.first_name"
                                >
                                <div class="invalid-feedback"> Please provide your first name </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Last name</label>
                                <input type="text" class="form-control"
                                    v-model.trim="form.last_name"
                                >
                                <div class="invalid-feedback"> Please provide your Last name</div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Email</label>
                                <input type="email" class="form-control"
                                    v-model.trim="form.email"
                                >
                                <div class="invalid-feedback"> Please provide a correct email </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Age</label>
                                <input type="number" step="1" class="form-control"
                                    v-model.trim="form.age"
                                >
                                <div class="invalid-feedback"></div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Start date</label>
                                <input type="date" step="1" class="form-control"
                                    v-model.trim="form.startDate"
                                >
                                <div class="invalid-feedback"></div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">End Date</label>
                                <input type="date" class="form-control"
                                    v-model.trim="form.endDate"
                                >
                                <div class="invalid-feedback"></div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Password</label>
                                <input type="password" class="form-control"
                                    v-model.trim="form.password"
                                >
                                <div class="invalid-feedback"> Please enter a password. </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Repeat your password</label>
                                <input type="password" class="form-control"
                                    v-model.trim="form.confirmPassword"
                                >
                                <div class="invalid-feedback"> Please re-enter password. </div>
                            </div>
                            <div class="mb-3 text-center">
                                <button class="btn btn-primary" type="submit">Submit</button>
                            </div>
                            <hr/>

                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</section>
</template>

Below is the Javascript part nothing fancy here, just the form data and the submission function.

<script>
export default {
  name: 'Registration',
  setup() {
    
  },
  data() {
    return {
      form: {
        first_name: '',
        last_name: '',
        email: '',
        age:'',
        startDate:'',
        endDate:'',
        password: '',
        confirmPassword: '',
      },
    }
  },
  methods: {
   async onSubmit() {
    },
  },
}
</script>

Now, we are ready to fill that form up with our Vue 3 validation codes, let’s get started.

Vuelidate Setup

We are going to use the Vuelidate package for the validation as it gets everything we need, let’s install it.

npm install @vuelidate/core @vuelidate/validators

Or

yarn add @vuelidate/core @vuelidate/validators

Let’s add the package to our component so that we can use it

import useVuelidate from '@vuelidate/core'
import { required, email, sameAs, between, minValue, maxValue, alpha, numeric, helpers} from '@vuelidate/validators'
export default {
//
 setup() {
    return { v$: useVuelidate() }
 },
//
}

To make the validation rules work after inputting the data and after clicking the submit button we need to create “setTouched” method to help us in the validation, Plus specifying the inputs with the rules in validations() method first.

//
validations() {
        return {
          form: {
            first_name: {
            },
            last_name: {
            },
            email: {
            },
            age: {
            },
            startDate: {
            },
            endDate: {
            },
            password: {
            },
            confirmPassword: {
            },
          },
        }
  },
  methods: {
    setTouched(theModel) {
        if(theModel == 'first_name' || theModel == 'all'){this.v$.form.first_name.$touch()} 
        if(theModel == 'last_name' || theModel == 'all'){this.v$.form.last_name.$touch()} 
        if(theModel == 'email' || theModel == 'all' ){this.v$.form.email.$touch()}
        if(theModel == 'age' || theModel == 'all'){this.v$.form.age.$touch()} 
        if(theModel == 'startDate' || theModel == 'all'){this.v$.form.startDate.$touch()} 
        if(theModel == 'endDate' || theModel == 'all'){this.v$.form.endDate.$touch()} 
        if(theModel == 'password' || theModel == 'all'){this.v$.form.password.$touch()} 
        if(theModel == 'confirmPassword' || theModel == 'all'){this.v$.form.confirmPassword.$touch()} 
    },
   async onSubmit(event) {
        event.preventDefault()
        this.setTouched('all');

        if (!this.v$.$invalid) 
        {
            alert('all Good')
        }
    },
  },

Vue 3 Validation Rules.

After installation let’s make All the input fields required.

  validations() {
        return {
          form: {
            first_name: {
                required,
            },
            last_name: {
                required,
            },
            email: {
                required,
            },
            age: {
                required,
            },
            startDate: {
                required,
            },
            endDate: {
                required,
            },
            password: {
                required,
            },
            confirmPassword: {
                required,
            },
          },
        }
  },

Let’s display the validation error message, inside the input element we add @input="setTouched('THE INPUT PARAMETER IN SET TOUCHED METHOD')" so that the validation rule can check.

And to check if there are errors v$.form.<INPUT NAME IN validations()>.$error, we can add an error class or display an error message as we want via :class="v$.form.first_name.$error?'is-invalid':''"

<template>
<section class="h-100 h-custom bg-light" >
    <div class="container py-5 h-100">
        <div class="row d-flex justify-content-center align-items-center h-100">
            <div class="col d-flex justify-content-center align-items-center">
                    
                <div class="card border-0 " style="min-width:350px; max-width:500px">
                    <div class="card-body">
                        <h3 class="mb-4">Sign up</h3>
                        <hr/>
                        <form @submit="onSubmit">
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">First name</label>
                                <input type="text" class="form-control"
                                    v-model.trim="form.first_name"
                                    @input="setTouched('first_name')"
                                    :class="v$.form.first_name.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback"> Please provide your first name </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Last name</label>
                                <input type="text" class="form-control"
                                    v-model.trim="form.last_name"
                                    @input="setTouched('last_name')"
                                    :class="v$.form.last_name.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback"> Please provide your Last name</div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Email</label>
                                <input type="email" class="form-control"
                                    v-model.trim="form.email"
                                    @input="setTouched('email')"
                                    :class="v$.form.email.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback"> Please provide a correct email </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Age</label>
                                <input type="number" step="1" class="form-control"
                                    v-model.trim="form.age"
                                    @input="setTouched('age')"
                                    :class="v$.form.age.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback">Please provide a age between 18 to 45</div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Start date</label>
                                <input type="date" step="1" class="form-control"
                                    v-model.trim="form.startDate"
                                    @input="setTouched('startDate')"
                                    :class="v$.form.startDate.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback">Please provide after Start date after 4/24/2023</div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">End Date</label>
                                <input type="date" class="form-control"
                                    v-model.trim="form.endDate"
                                    @input="setTouched('endDate')"
                                    :class="v$.form.endDate.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback">Please provide after end date after the start date</div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Password</label>
                                <input type="password" class="form-control"
                                    v-model.trim="form.password"
                                    @input="setTouched('password')"
                                    :class="v$.form.password.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback"> Please enter a password. </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Repeat your password</label>
                                <input type="password" class="form-control"
                                    v-model.trim="form.confirmPassword"
                                    @input="setTouched('confirmPassword')"
                                    :class="v$.form.confirmPassword.$error?'is-invalid':''"
                                >
                                <div class="invalid-feedback"> Please re-enter password. </div>
                            </div>
                            <div class="mb-3 text-center">
                                <button class="btn btn-primary" type="submit">Submit</button>
                            </div>
                            <hr/>

                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</section>
</template>

If you clicked the submit button you should see the error messages.

Error Validation Messages

let’s go deeper with displaying the error validation message with Vuelidate, Let’s add another validation rule alpha for the first name to make sure that the value is alphabetical.

validations() {
        return {
          form: {
            first_name: {
                required,
                alpha,
            },
//
          },
        }
  },

Now, we have two error messages, let’s edit the first name input so that we let Vuelidate display the messages by itself.

 <!-- Input--------------------->
<div class="mb-3">
  <label class="form-label">First name</label>
  <input type="text" class="form-control"
    v-model.trim="form.first_name"
    @input="setTouched('first_name')"
    :class="v$.form.first_name.$error?'is-invalid':''"
  >
  <div v-for="error of v$.form.first_name.$errors" class="invalid-feedback"  :key="error.$uid"> 
   {{ error.$message }}
  </div>
</div>

We used v$.form.first_name.$errors to loop all the error messages. now try to enter numbers in the input, it should display “The value is not alphabetical”.

Another easy way for displaying the message is by creating a list of all errors at the top of the form.

<!-- // -->
<div class="card border-0 " style="min-width:350px; max-width:500px">
   <div class="card-body">
     <h3 class="mb-4">Sign up</h3>
     <hr/>
      <div v-if="v$.$errors.length > 0" class="alert alert-danger" role="alert">
        <ul>
          <li
            v-for="error of v$.$errors"
            :key="error.$uid"
           >
            <strong>{{ error.$validator }}</strong>
            <small> on property </small>
            <strong>{{ error.$property }}</strong>
            <small> says: </small>
            <strong>{{ error.$message }}</strong>
          </li>
        </ul>
     </div>
<form @submit="onSubmit">
<!-- // -->

We check if there is an error or not via v-if="v$.$errors.length > 0" then we display the message in the ul element

Custom Error Messages

Sometimes we need to translate or create a custom error message, it is so easy to customize the messages. So let’s try that on the last name input.

  validations() {
        return {
          form: {
            first_name: {
                required,
                alpha
            },
            last_name: {
                required,
                alpha: helpers.withMessage('Seriously a number in your name', alpha)
            },
 //
          },
        }
  },
 <!-- Input--------------------->
<div class="mb-3">
  <label class="form-label">Last name</label>
  <input type="text" class="form-control"
  v-model.trim="form.last_name"
  @input="setTouched('last_name')"
  :class="v$.form.last_name.$error?'is-invalid':''"
  >
<div v-for="error of v$.form.last_name.$errors" class="invalid-feedback"  :key="error.$uid"> 
  {{ error.$message }}
 </div>
</div>

Custom Validators

We need to create a custom validation rule to make sure the start date is greater than today. We will create dateBefore validators just before export default.

//
/*
*@theDate '04/24/2023'
*/
const dateBefore = (value) => {
    console.log(value)
    return new Date(value) > new Date()
}

export default {
//

Let’s use dateBefore inside validations()

startDate: {
            required,
            dateAfter: helpers.withMessage('The date must be after today', dateBefore),    
            },

Let’s add another Validator inside validations() to make sure that the end date is greater than the start date.

endDate: {
          required,
          minValue: helpers.withMessage('End date must be after the start date', value => {
          console.log(value)
          return new Date(value) > new Date(this.form.startDate)
          }), 
                
         },

More Rules

We have learned how to create validation rules and display error messages and customize them. Let’s put minimum and maximum length for names input.

  validations() {
        return {
          form: {
            first_name: {
                required,
                alpha,
                minLength: minLength(3),
                maxLength: maxLength(15)
            },
            last_name: {
                required,
                alpha: helpers.withMessage('Seriously a number in your name', alpha),
                minLength: minLength(3),
                maxLength: maxLength(15)
            },
 //
          },
        }
  },

Let’s make the age input between 18 and 45 years old

age: {
         required,
         between: between(18,45)
     },

Let’s validate the email

 email: {
             required,
             email
         },

Finally, let’s validate the password and make sure that it matches the password confirmation.

password: {
            required,
           },
confirmPassword: {
            required,
            confirmPassword: sameAs(this.form.password)
          },

Vue 3 Form Validation Example

Here is the whole component with the template and script.

<template>
<section class="h-100 h-custom bg-light" >
    <div class="container py-5 h-100">
        <div class="row d-flex justify-content-center align-items-center h-100">
            <div class="col d-flex justify-content-center align-items-center">
                    
                <div class="card border-0 " style="min-width:350px; max-width:500px">
                    <div class="card-body">
                        <h3 class="mb-4">Sign up</h3>
                        <hr/>
                        <div v-if="v$.$errors.length > 0" class="alert alert-danger" role="alert">
                            <ul>
                                <li
                                    v-for="error of v$.$errors"
                                    :key="error.$uid"
                                    >
                                    <strong>{{ error.$validator }}</strong>
                                    <small> on property </small>
                                    <strong>{{ error.$property }}</strong>
                                    <small> says: </small>
                                    <strong>{{ error.$message }}</strong>
                                </li>
                            </ul>
                        </div>
                        <form @submit="onSubmit">
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">First name</label>
                                <input type="text" class="form-control"
                                    v-model.trim="form.first_name"
                                    @input="setTouched('first_name')"
                                    :class="v$.form.first_name.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.first_name.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Last name</label>
                                <input type="text" class="form-control"
                                    v-model.trim="form.last_name"
                                    @input="setTouched('last_name')"
                                    :class="v$.form.last_name.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.last_name.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Email</label>
                                <input type="email" class="form-control"
                                    v-model.trim="form.email"
                                    @input="setTouched('email')"
                                    :class="v$.form.email.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.email.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Age</label>
                                <input type="number" step="1" class="form-control"
                                    v-model.trim="form.age"
                                    @input="setTouched('age')"
                                    :class="v$.form.age.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.age.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Start date</label>
                                <input type="date" step="1" class="form-control"
                                    v-model.trim="form.startDate"
                                    @input="setTouched('startDate')"
                                    :class="v$.form.startDate.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.startDate.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">End Date</label>
                                <input type="date" class="form-control"
                                    v-model.trim="form.endDate"
                                    @input="setTouched('endDate')"
                                    :class="v$.form.endDate.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.endDate.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Password</label>
                                <input type="password" class="form-control"
                                    v-model.trim="form.password"
                                    @input="setTouched('password')"
                                    :class="v$.form.password.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.password.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <!-- Input--------------------->
                            <div class="mb-3">
                                <label class="form-label">Repeat your password</label>
                                <input type="password" class="form-control"
                                    v-model.trim="form.confirmPassword"
                                    @input="setTouched('confirmPassword')"
                                    :class="v$.form.confirmPassword.$error?'is-invalid':''"
                                >
                                <div v-for="error of v$.form.confirmPassword.$errors" class="invalid-feedback"  :key="error.$uid"> 
                                    {{ error.$message }}
                                </div>
                            </div>
                            <div class="mb-3 text-center">
                                <button class="btn btn-primary" type="submit">Submit</button>
                            </div>
                            <hr/>

                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
    {{ form.startDate }}
</section>
</template>
<script>
import useVuelidate from '@vuelidate/core'
import { required, email, sameAs, between, minValue, maxValue, alpha, numeric, helpers,minLength ,maxLength } from '@vuelidate/validators'
/*
*@theDate '04/24/2023'
*/
const dateBefore = (value) => {
    console.log(value)
    return new Date(value) > new Date()
}

export default {
  name: 'Registration',
  setup() {
    return { v$: useVuelidate() }
  },
  data() {
    return {
      form: {
        first_name: '',
        last_name: '',
        email: '',
        age:'',
        startDate:"ssdsdsd",
        endDate:'',
        password: '',
        confirmPassword: '',
      },
    }
  },
  validations() {
        return {
          form: {
            first_name: {
                required,
                alpha,
                minLength: minLength(3),
                maxLength: maxLength(15)
            },
            last_name: {
                required,
                alpha: helpers.withMessage('seriously a number in your name', alpha),
                minLength: minLength(3),
                maxLength: maxLength(15)
            },
            email: {
                required,
                email
            },
            age: {
                required,
                between: between(18,45)
            },
            startDate: {
                required,
                dateAfter: helpers.withMessage('The date must be after today', dateBefore),    
            },
            endDate: {
                required,
                minValue: helpers.withMessage('End date must be after the start date', value => {
                console.log(value)
                return new Date(value) > new Date(this.form.startDate)
                }), 
                
            },
            password: {
                required,
            },
            confirmPassword: {
                required,
                confirmPassword: sameAs(this.form.password)
            },
          },
        }
  },
  methods: {
    setTouched(theModel) {
        if(theModel == 'first_name' || theModel == 'all'){this.v$.form.first_name.$touch()} 
        if(theModel == 'last_name' || theModel == 'all'){this.v$.form.last_name.$touch()} 
        if(theModel == 'email' || theModel == 'all' ){this.v$.form.email.$touch()}
        if(theModel == 'age' || theModel == 'all'){this.v$.form.age.$touch()} 
        if(theModel == 'startDate' || theModel == 'all'){this.v$.form.startDate.$touch()} 
        if(theModel == 'endDate' || theModel == 'all'){this.v$.form.endDate.$touch()} 
        if(theModel == 'password' || theModel == 'all'){this.v$.form.password.$touch()} 
        if(theModel == 'confirmPassword' || theModel == 'all'){this.v$.form.confirmPassword.$touch()}
    },
   async onSubmit(event) {
        event.preventDefault()
        this.setTouched('all');
        if (!this.v$.$invalid) 
        {
            alert('all Good')
        }
    },
  },
}
</script>

That’s all, To learn more about date time validation, see this tutorial: Vuelidate Date And Time Validation Example Tutorial. Thank you