Tutorial

How To Validate Forms in Vue.js

DevelopmentJavaScriptVue.js

Introduction

Form validation, also known as form field validation, ensures that a user fills out all required fields in a web form. If a field has an invalid value, it will display an error message and prevent form submission until the values satisfy all the rules.

Template-driven validation is a type of form validation where validation rules are set directly in the form elements using directives.

To implement template-driven validations in Vue.js, we can use VeeValidate. VeeValidate is a plugin for Vue.js that allows you to validate input fields and display errors.

Here is an animated image of what you will build in this tutorial:

Animated gif of form validation with VeeValidate

At the end of this tutorial, you will have a registration form that uses VeeValidate to validate the input fields.

Prerequisites

This tutorial assumes knowledge of JavaScript strings and objects. Some familiarity with Vue will be beneficial but is not required. To learn more about Javascript, check out the How To Code in Javascript series.

We will focus on building a single local HTML file with references to various cloud-hosted libraries. It is possible to use @vue/cli to create a Vue project and use a package manager to install vee-validate; however, that approach is outside of this tutorial’s scope.

Step 1 — Setting Up a Vue.js Project with VeeValidate

You will need the Vue.js framework and the VeeValidate library.

First, use your terminal to create a new file called register.html:

  • nano register.html

And add some initial code for a webpage:

register.html
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>Vue Template Form Validation</title>
</head>
<body>

  <!-- ... -->

</body>
</html>

A browser build for Vue.js is available via cdnjs. A browser build for VeeValidate is available via jsdelivr. Add both to the <body> of the register.html file:

register.html
<body>

  <!-- include the Vue.js framework -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>

  <!-- include the VeeValidate library -->
  <script src="https://cdn.jsdelivr.net/npm/vee-validate@3.3.8/dist/vee-validate.min.js"></script>

</body>

These files are provided by CDNs (content delivery networks). There is nothing to download or save locally.

You now have a webpage ready to use the latest stable versions (as of this writing) of Vue.js and VeeValidate.

Adding Bootstrap and Font Awesome

To establish some styling, you can use Bootstrap. To add some iconography, you can also utilize Font Awesome.

Browser builds for Bootstrap and Font Awesome are available via BootstrapCDN. Add both to the <head> of the register.html file:

register.html
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>Vue Template Form Validation</title>

  <!-- include the Bootsrap framework -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

  <!-- include Font Awesome -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
</head>

At this point, you have Vue, VeeValidate, Bootstrap, and Font Awesome. Next, you will create the form to validate.

Constructing the Form Markup

This example form will seek five pieces of information from the user. You will need a name, email, username, password, and password_confirmation.

First, add some initial markup for the form to the <body> of the register.html file before the <script> tags:

register.html
<body>
  <div class="container my-3">
    <div class="row justify-content-around">
      <div class="col-6 rounded shadow">
        <h1 class="py-3">Sign up once and watch any of our free demos.</h1>
        <div id="signup-form">
          <form>

          <!-- ... form fields ... -->

          </form>
        </div>
      </div>
    </div>
  </div>

  <!-- ... library script tags ... -->
</body>

This code establishes an empty <form> and uses some Bootstrap utilities for layout and appearance.

Next, add the form fields to the <form>. Start with the field for name:

register.html
<form>

  <div class="form-group">
    <label for="name">Your Name</label>
    <div class="input-group">
      <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-user" aria-hidden="true"></i></span></span>
      <input type="text" id="name" name="name" placeholder="Name" class="form-control" />
    </div>
  </div>

</form>

This code creates a <label> for name, a Font Awesome icon for fa-user, and an <input> for name.

You can make similar additions to the <form> for the other pieces of information—email, username, password, and password_confirmation:

register.html
<form>

  <!-- ... name ... -->

  <div class="form-group">
    <label for="email">Your Email</label>
    <div class="input-group">
      <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-envelope" aria-hidden="true"></i></span></span>
      <input type="email" id="email" name="email" placeholder="email@example.com" class="form-control" />
    </div>
  </div>

  <div class="form-group">
    <label for="username">Username</label>
    <div class="input-group">
      <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-users" aria-hidden="true"></i></span></span>
      <input type="text" id="username" name="username" placeholder="Enter your username" class="form-control" />
    </div>
  </div>

  <div class="form-group">
    <label for="password">Password</label>
    <div class="input-group">
      <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-lock" aria-hidden="true"></i></span></span>
      <input type="password" id="password" name="password" placeholder="Enter a password" class="form-control" />
    </div>
  </div>

  <div class="form-group">
    <label for="password_confirmation">Confirm Password</label>
    <div class="input-group">
      <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-lock" aria-hidden="true"></i></span></span>
      <input type="password" id="password_confirmation" name="password_confirmation" placeholder="Re-type password" class="form-control" />
    </div>
  </div>

</form>

This code creates <label>s, a Font Awesome icons, and <input>s. Each input has a unique id and name.

Add a button for registration to complete the <form>:

register.html
<form>

  <!-- ... form fields ... -->

  <div class="form-group">
    <button type="submit" class="btn btn-block btn-lg btn-primary">Register</button>
  </div>

</form>

This code creates a large submit button using Bootstrap styles.

You can open register.html in a web browser to check the progress of the app.

Step 2 — Creating and Mounting a Vue Instance

Next, you will create a Vue instance and mount it to the #signup-form.

Add a new <script> tag at the end of the <body> and define signupForm:

register.html
<body>
  <!-- ... form ... -->
  <!-- ... library script tags ... -->

  <script>
    const signupForm = new Vue({
      el: '#signup-form'
    });
  </script>
</body>

Add properties to the data object:

register.html
<body>
  <!-- ... form ... -->
  <!-- ... library script tags ... -->

  <script>
    const signupForm = new Vue({
      el: '#signup-form',
      data: {
        name: '',
        email: '',
        username: '',
        password: '',
        password_confirmation: ''
      }
    });
  </script>
</body>

Then, reference the properties with v-model in each of the fields.

For the name field, add the following:

register.html
<input type="text" id="name" name="name" placeholder="Name" class="form-control" v-model="name" />

For the email field, add the following:

register.html
<input type="email" id="email" name="email" placeholder="email@example.com" class="form-control" v-model="email" />

For the username field, add the following:

register.html
<input type="text" id="username" name="username" placeholder="Enter your username" class="form-control" v-model="username" />

For the password field, add the following:

register.html
<input type="password" id="password" name="password" placeholder="Enter a password" class="form-control" v-model="password" />

Finally, for the password_confirmation field, add the following:

register.html
<input type="password" id="password_confirmation" name="password_confirmation" placeholder="Re-type password" class="form-control" v-model="password_confirmation" />

At this point, you have a Vue instance with models for name, email, username, password, and password_confirmation.

Step 3 — Adding ValidationObserver and ValdiationProvider

Next, you will need to register ValidationObserver and ValidationProvider.

You can add both to a new <script> tag at then end of the <body>:

register.html
<body>
  <!-- ... form ... -->
  <!-- ... library script tags ... -->

  <script>
    Vue.component('validation-observer', VeeValidate.ValidationObserver);

    Vue.component('validation-provider', VeeValidate.ValidationProvider);
  </script>

  <!-- ... vue instance script tag ... -->
</body>

Now, you can use <validation-observer> to wrap the entire <form>. And you can use <validation-provider> to wrap the fields:

register.html
<validation-observer>

  <form>

    <div class="form-group">
      <validation-provider>
        <label for="name">Your Name</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-user" aria-hidden="true"></i></span></span>
          <input type="text" id="name" name="name" placeholder="Name" class="form-control" v-model="name" />
        </div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider>
        <label for="email">Your Email</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-envelope" aria-hidden="true"></i></span></span>
          <input type="email" id="email" name="email" placeholder="email@example.com" class="form-control" v-model="email" />
        </div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider>
        <label for="username">Username</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-users" aria-hidden="true"></i></span></span>
          <input type="text" id="username" name="username" placeholder="Enter your username" class="form-control" v-model="username" />
        </div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider>
        <label for="password">Password</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-lock" aria-hidden="true"></i></span></span>
          <input type="password" id="password" name="password" placeholder="Enter a password" class="form-control" v-model="password" />
        </div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider>
        <label for="password_confirmation">Confirm Password</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-lock" aria-hidden="true"></i></span></span>
          <input type="password" id="password_confirmation" name="password_confirmation" placeholder="Re-type password" class="form-control" v-model="password_confirmation" />
        </div>
      </validation-provider>
    </div>

    <div class="form-group">
      <button type="submit" class="btn btn-block btn-lg btn-primary">Register</button>
    </div>

  </form>

</validation-observer>

You now have a form prepared with <validation-observer> and <valdiation-provider>.

Step 4 — Using VeeValidate Rules

A VeeValidate rule sets limits or conditions on what can be entered in one or more fields. Validation rules are checked when you update a record containing fields requiring validation. If the rule is violated, a trappable error occurs.

For example, you can use the required validator:

<validation-provider rules="required">

You can pass multiple validations separated by a pipe character (|).

For example, you can use the required and the email validators:

<validation-provider rules="required|email">

Alternatively, you can pass an object for more flexibility:

<validation-provider :rules="{ required: true, email: true, regex: /[0-9]+/ }">

Now every time the input changes, the validator will run the list of validations from left to right, populating the errors helper object whenever an input fails validation.

As at the time of writing this tutorial, VeeValidate has 30 rules for form validation with an option of creating your own rules.

Applying Rules

Next, you need to import the VeeValidateRules.

You can add it to a new <script> tag at then end of the <body> of in the register.html file:

register.html
<body>
  <!-- ... form ... -->
  <!-- ... library script tags ... -->

  <script src="https://cdn.jsdelivr.net/npm/vee-validate@3.3.8/dist/rules.umd.js"></script>

  <!-- ... vue instance tags ... -->
</body>

Then, you can loop over the rules to make them all available:

register.html
<body>
  <!-- ... form ... -->
  <!-- ... library script tags ... -->

  <script>
    Object.keys(VeeValidateRules).forEach(rule => {
      VeeValidate.extend(rule, VeeValidateRules[rule]);
    });
  </script>

  <!-- ... vue instance tags ... -->
</body>

And apply required rules for all the inputs:

register.html
<validation-provider rules="required">

Applying Multiple Rules

For email, you will also apply a rule for valid email addresses:

register.html
<validation-provider rules="required|email">

For password you will also apply a rule for a minimum length of 6 characters:

register.html
<validation-provider rules="required|min:6">

Now, you have required, email, and min rules for the fields.

Applying Cross-Field Validation

For password_confirmation you will need to match the value of password to be valid. To accomplish this, you will rely on ValidationObserver, which allows password_confirmation to be aware of password.

Add the vid to the password field, so password_confirmed has a target:

register.html
<validation-provider rules="required|min:6" vid="password">

Add the confirmed rule to the password_confirmation field, so password_confirmed compares its value against the value for password:

register.html
<validation-provider rules="required|confirmed:password">

Now, you have required, email, min, and confirmed rules for the fields.

Adding a Custom Rule

VeeValidate allows you to write custom validation rules and messages with extend and validate.

Add a rule that prevents users from registering with some restricted words. In this example, you will restrict users from using the words admin, password, and administrator:

register.html
<body>
  <!-- ... form ... -->
  <!-- ... library script tags ... -->

  <script>
    // Declare an array of usernames that are invalid.
    const restricted_usernames = [
      'admin',
      'password',
      'administrator'
    ];

    // Extend the custom rule.
    VeeValidate.extend('checkuser', {
      name: 'Restricted Usernames',
      validate: value => {
        return restricted_usernames.includes(value.toLowerCase()) ? false : !! value
      },
      message: 'That {_field_} is unavailable.',
    });
  </script>

  <!-- ... vue instance tags ... -->
</body>

Add the custom rule to the username field:

register.html
<validation-provider rules="required|checkuser">

Now, you have required, email, min, confirmed, and checkuser rules for the fields. The rules are all established, and now you can start displaying error messages.

Step 5 — Accessing VeeValidate Errors and Flags

VeeValidate has errors available. VeeValidate also has multiple flags for state information. You can access these using Vue’s v-slot.

You will also use Vue’s v-show to display the VeeValidate error messages and use Bootstrap’s invalid-feedback class for styling the errors.

Additionally, you will use VeeValidate flags for dirty, valid, and invalid in combination with Vue’s v-bind:class and Bootstrap’s is-valid and is-invalid classes for styling the fields:

register.html
<validation-observer>

  <form>

    <div class="form-group">
      <validation-provider rules="required|alpha" v-slot="{ dirty, valid, invalid, errors }">
        <label for="name">Your Name</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-user" aria-hidden="true"></i></span></span>
          <input type="text" id="name" name="name" placeholder="Name" class="form-control" v-model="name" v-bind:class="{ 'is-valid': dirty && valid, 'is-invalid': dirty && invalid }" />
        </div>
        <div class="invalid-feedback d-inline-block" v-show="errors">{{ errors[0] }}</div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider rules="required|email" v-slot="{ dirty, valid, invalid, errors }">
        <label for="email">Your Email</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-envelope" aria-hidden="true"></i></span></span>
          <input type="email" id="email" name="email" placeholder="email@example.com" class="form-control" v-model="email" v-bind:class="{ 'is-valid': dirty && valid, 'is-invalid': dirty && invalid }" />
        </div>
        <div class="invalid-feedback d-inline-block" v-show="errors">{{ errors[0] }}</div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider rules="required|checkuser" v-slot="{ dirty, valid, invalid, errors }">
        <label for="username">Username</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-users" aria-hidden="true"></i></span></span>
          <input type="text" id="username" name="username" placeholder="Enter your username" class="form-control" v-model="username" v-bind:class="{ 'is-valid': dirty && valid, 'is-invalid': dirty && invalid }" />
        </div>
        <div class="invalid-feedback d-inline-block" v-show="errors">{{ errors[0] }}</div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider rules="required|min:6" vid="password" v-slot="{ dirty, valid, invalid, errors }">
        <label for="password">Password</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-lock" aria-hidden="true"></i></span></span>
          <input type="password" id="password" name="password" placeholder="Enter a password" class="form-control" v-model="password" v-bind:class="{ 'is-valid': dirty && valid, 'is-invalid': dirty && invalid }" />
        </div>
        <div class="invalid-feedback d-inline-block" v-show="errors">{{ errors[0] }}</div>
      </validation-provider>
    </div>

    <div class="form-group">
      <validation-provider rules="required|confirmed:password" v-slot="{ dirty, valid, invalid, errors }">
        <label for="password_confirmation">Confirm Password</label>
        <div class="input-group">
          <span class="input-group-prepend"><span class="input-group-text"><i class="fa fa-lock" aria-hidden="true"></i></span></span>
          <input type="password" id="password_confirmation" name="password_confirmation" placeholder="Re-type password" class="form-control" v-model="password_confirmation" v-bind:class="{ 'is-valid': dirty && valid, 'is-invalid': dirty && invalid }" />
        </div>
        <div class="invalid-feedback d-inline-block" v-show="errors">{{ errors[0] }}</div>
      </validation-provider>
    </div>

    <div class="form-group">
      <button type="submit" class="btn btn-block btn-lg btn-primary">Register</button>
    </div>

  </form>

</validation-observer>

At this point, you have access to dirty, valid, invalid, and errors. You added logic to display error messages as feedback under the associated field. If the field is interacted with and invalid, it will apply Bootstrap’s is-invalid class. If the field is interacted with and valid, it will apply Bootstrap’s is-valid class.

In the next step, you will handle submitting the form.

Step 6 — Handling Form Submission

VeeValidate also provides an invalid flag for ValidationObserver and a handleSubmit function. You can access these using Vue’s v-slot:

register.html
<validation-observer v-slot="{ invalid, handleSubmit }">

Use Vue’s event modifiers to capture form submission with @submit.prevent. You will also use VeeValidate’s handleSubmit to prevent form submission until all fields are valid:

register.html
<form @submit.prevent="handleSubmit(onSubmit)">

This will call onSubmit which can be defined as a console.log message:

register.html
<script>
  const signupForm = new Vue({
    el: '#signup-form',
    data: {
      name: '',
      email: '',
      username: '',
      password: '',
      password_confirmation: ''
    },
    methods: {
      onSubmit: function() {
        console.log('Form has been submitted!');
      }
    }
  });
</script>

Keep the <button> in a disabled state so it will not submit information while any fields are invalid:

register.html
<button type="submit" class="btn btn-block btn-lg btn-primary" v-bind:disabled="invalid">Register</button>

At this point, you can open register.html in a web browser and interact with the form to test the validations.

Conclusion

In this tutorial, you have demonstrated how to validate form inputs using the template-driven approach. VeeValidate has allowed you to validate form inputs with existing rules, extend new rules, display errors, and handle the form submission.

If you’d like to learn more about Vue.js, check out our Vue.js topic page for exercises and programming projects.

Creative Commons License