Introduction
Form validation ensures that a user enters the correct data and meets the criteria defined based on the expected format before submitting the data. Examples of form validation include ensuring that email is in the correct format i.e. username followed by @ symbol and a domain name, ensuring that required input fields are not left blank, ensuring that passwords meet certain requirements e.g. password must not be less than eight (8) characters.
Vuelidate is a simple, lightweight model-based validation package for Vue.js. Vuelidate makes form validation straightforward because the validation rules are similar to the data model structure used by the form.
In this tutorial, you will learn how to use vuelidate to validate forms in a vue.js application.
Prerequisites
- Good Understanding of HTML, CSS, and JavaScript
- Basic Understanding of Vue.js
Setting up a Vue application
To learn about vuelidate, you will set up a vue.js application. This application is what would contain the form to be validated using vuelidate.
npm create vue@latest
The command above installs and executes create-vue, the official vue project scaffolding tool. The command would trigger a prompt with Vue.js optional features that you may need for your project.
The optional features would be presented as such in your command line.
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Nightwatch / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Vue DevTools 7 extension for debugging? (experimental) … No / Yes
Choose whatever option that suits your use case best.
Install all the dependencies that come with a vue application
npm install
The next step is to clear out the default codes that come with a fresh vue application.
Creating a form
Open src/views/HomeView.vue
and create a form
<script setup>
import {reactive, computed} from "vue";
const registrationData = reactive({
username: "",
email: "",
tel: "",
age: "",
password: "",
confirm_password: "",
});
function submit() {
alert("Form submitted")
}
</script>
Here we have a registrationData object that is a reactive data model for the two-way data binding in the form input
fields. Then a submit
function that would handle the form submission.
<template>
<main>
<form @submit.prevent="submit">
<div>
<label for="username">Username</label>
<input
type="text"
id="username"
placeholder="Enter your username"
v-model="registrationData.username"
/>
</div>
<div>
<label for="email">Email</label>
<input
type="email"
id="email"
placeholder="Enter your email"
v-model="registrationData.email"
/>
</div>
<div>
<label for="tel">Tel</label>
<input
type="tel"
id="tel"
placeholder="Enter your phone number"
v-model="registrationData.tel"
/>
</div>
<div>
<label for="age">Age</label>
<input
type="number"
id="age"
placeholder="Enter your age"
v-model="registrationData.age"
/>
</div>
<div>
<label for="password">Password</label>
<input
type="password"
id="password"
placeholder="Enter your password"
v-model="registrationData.password"
/>
</div>
<div>
<label for="confirm_password">Confirm password</label>
<input
type="password"
id="confirm_password"
placeholder="Confirm your password"
v-model="registrationData.confirm_password"
/>
</div>
<button>Submit</button>
</form>
</main>
</template>
This is the registration form markup with common input fields that appear on registration forms.
main {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-height: 100vh;
background-color: #d1d5db;
}
form {
width: 500px;
background: white;
padding: 16px;
}
form > div {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
}
form > div > input {
border: 1px solid #9ca3af;
padding: 4px 6px;
}
form > div > label {
font-size: 14px;
font-weight: 600;
color: #252525;
}
form > div > span {
font-size: 12px;
color: #dc2626;
}
input:focus {
border: none;
outline: 1px solid #000;
}
button {
width: 100%;
border: none;
background-color: #6941c6;
color: white;
padding: 10px 0;
}
At this point the registration form is all set up. This is what the form should look like at this point.
Setting up vuelidate
Installation
To use vuelidate in this application, the first step is to install vuelidate
npm install @vuelidate/core @vuelidate/validators
// OR
yarn add @vuelidate/core @vuelidate/validators
This command installs the two important vuelidate packages @vuelidate/core
and @vuelidate/validators
.
Adding to the form
To add vuelidate to our form, we need to first import the vuelidate package and vuelidate validators. Vuelidate provides a lot of built-in validators. We are going to cover some of them in this tutorial.
import {useVuelidate} from "@vuelidate/core";
import {
required,
email,
minLength,
minValue,
sameAs,
helpers,
alphaNum,
} from "@vuelidate/validators";
The useVuelidate
function is used to activate Vuelidate inside the component. This function accepts the validation
rules and the data model. The next imports are the built-in validators that we need for this form. The next step is to
define the validation rules.
const rules = {
username: {required, minLength: minLength(3), alphaNum},
email: {required, email},
tel: {
required,
},
age: {required, minValue: minValue(18)},
password: {required, minLength: minLength(8)},
confirm_password: {
required,
sameAs: sameAs(registrationData.password),
},
}
This is the rules object. Its keys should match exactly those of the reactive data model. Here are explanations of the rules we defined.
required
: This validator makes sure that the input field is not empty.minLength
: This is for the minimum length of strings. In the code above, the minimum length of the input cannot be less than three (3).alphaNum
: This validator ensures that the input contains only alphanumeric characters.email
: This validates email to make sure that the email is in the correct format.minValue
: This is for numbers, this validator makes sure the input value is not less than the defined rule. In the rule above, we don't want users below the age of 18sameAs
: This validator checks if two input values are the same. It accepts the value to be checked for equality.
To initialize vuelidate within this component, we create a variable v$
and set it to the useVuelidate()
.
The useVuelidate()
function accepts two arguments the rules and the data object.
const v$ = useVuelidate(rules, registrationData);
The form is validated at the point of submission when the user submits the form. This is done using the v$.validate()
function.
async function submit() {
const isValid = await v$.value.$validate();
if (isValid) {
// submit form
} else {
// handle error
}
}
The v$.value.$validate()
is an asynchronous function which returns true
or false
. It returns true
when the form
passes the validation rules and false
when the form does not meet the validation rules.
<script setup>
import {ref, reactive, computed} from "vue";
import {useVuelidate} from "@vuelidate/core";
import {
required,
email,
minLength,
minValue,
sameAs,
helpers,
alphaNum,
} from "@vuelidate/validators";
const registrationData = reactive({
username: "",
email: "",
tel: "",
age: "",
password: "",
confirm_password: "",
});
const rules = {
username: {required, minLength: minLength(3), alphaNum},
email: {required, email},
tel: {
required,
},
age: {required, minValue: minValue(18)},
password: {required, minLength: minLength(8)},
confirm_password: {
required,
sameAs: sameAs(registrationData.password),
},
}
const v$ = useVuelidate(rules, registrationData);
async function submit() {
const isValid = await v$.value.$validate();
if (isValid) {
// submit form
} else {
// handle error
}
}
</script>
Displaying error messages
There are two ways to display error messages when using vuelidate.
- Grouping all the error messages at the end of the form.
- Displaying an individual error message close to the input field associated with the error message.
To group all the error messages and display them at the end of the form, we need to first understand that the errors are
stored as an array of objects in the v$.errors
property. To add this to the form, we would add a p
element and then
loop through the $error
array, displaying the messages accordingly.
<p v-for="error in v$.$errors" :key="error.$uid"> {{ error.$property }} - {{ error.$message }},</p>
error.$uid
is a unique identifier for each message.error.$property
shows the name of the filed where the validation failederror.$message
shows the error message.
![Grouped error message](res.cloudinary.com/silkydev/image/upload/v1.. "Grouped error")
This is what the form would look like using the grouped error messages. This is not the best way to display errors
because the messages are clustered and some users might find it difficult to locate a particular input field that has
a particular error. The other way of displaying error messages is displaying individual error messages right next to
the input field associated with the message. These messages are also stored as an array of objects but on
the v$.${property_name}.$errors
.
To display this error on the template, we would add a span element next to an input and loop through the error associated with that input field. E.g to display an error associated with the username field we would have something like this.
<span v-for="error in v$.username.$errors" :key="error.$uid">{{ error.$message }}</span>
Adding the code above for every other input field.
<main>
<form @submit.prevent="submit">
<div>
<label for="username">Username</label>
<input
type="text"
id="username"
placeholder="Enter your username"
v-model="registrationData.username"
/>
<span v-for="error in v$.username.$errors" :key="error.$uid">
{{error.$message }}
</span>
</div>
<div>
<label for="email">Email</label>
<input
type="email"
id="email"
placeholder="Enter your email"
v-model="registrationData.email"
/>
<span v-for="error in v$.email.$errors" :key="error.$uid">
{{ error.$message }}
</span>
</div>
<div>
<label for="tel">Tel</label>
<input
type="tel"
id="tel"
placeholder="Enter your phone number"
v-model="registrationData.tel"
/>
<span v-for="error in v$.tel.$errors" :key="error.$uid">
{{ error.$message }}
</span>
</div>
<div>
<label for="age">Age</label>
<input
type="number"
id="age"
placeholder="Enter your age"
v-model="registrationData.age"
/>
<span v-for="error in v$.age.$errors" :key="error.$uid">
{{ error.$message}}
</span>
</div>
<div>
<label for="password">Password</label>
<input
type="password"
id="password"
placeholder="Enter your password"
v-model="registrationData.password"
/>
<span v-for="error in v$.password.$errors" :key="error.$uid">
{{error.$message }}
</span>
</div>
<div>
<label for="confirm_password">Confirm password</label>
<input
type="password"
id="confirm_password"
placeholder="Confirm your password"
v-model="registrationData.confirm_password"
/>
<span v-for="error in v$.confirm_password.$errors" :key="error.$uid">
{{error.$message}}
</span>
</div>
<button>Submit</button>
</form>
</main>
At this point, if the form is submitted without meeting the validation rules, error messages would be displayed next to the input field associated with them.
Custom Error Message
Custom error message is used to change the default error message that comes with vuelidate. This is done using
the withMessage()
helper function. The withMessage()
helper accepts two arguments. The first is the custom message
and the validator is the second.
import {helpers, required} from "@vuelidate/validators";
const rules = {
name: {required: helpers.withMessage("Please enter your full government name", required)}
}
Custom validator
A custom validator is a function used to add a custom validation rule. These can be extra validation rules that you need but are not part of Vuelidate's built-in validators. For example, you do not want the username input field to have placeholder values like John Doe or Jane Doe.
import {helpers, required} from "@vuelidate/validators";
const checkUsername = (value) => {
return !(value.toLowerCase() === "john doe" || value.toLowerCase() === "jane doe");
}
const rules = {
username: {required, checkUsername: helpers.withMessage("Your cannot register with this username", checkUsername)}
}
The checkUsername
function checks the username to determine if it is "john doe" or "jane doe". This function has a
parameter called value
that is the input value. To use the custom validator, it is used in the same way as the
built-in
validators. The withMessage()
helper adds a custom error message.
The $dirty
state
Vuelidate uses the $dirty
state to check whether any input has been interacted with. The $dirty
state is set to
false
by default until a user interacts with it.
The $dirty
state can be programmatically updated using the $touch
method, this validates the input and outputs
proper error messages if there are any. This is done by calling v$.propertyname.touch
on the input field using any
input event handler.
<div>
<label for="username">Username</label>
<input
type="text"
id="username"
placeholder="Enter your username"
v-model="registrationData.username"
@blur="v$.username.$touch" // touch method
/>
<span v-for="error in v$.username.$errors" :key="error.$uid">
{{ error.$message}}
</span>
</div>
v$.username.$touch
is called using the blur
event handler. This validates the input when the user leaves the input
field.
Conclusion
In this article, you learnt how to use Vuelidate to validate forms in Vue.js. You also learnt about the built-in validators that Vuelidate provides and how to define your custom validator. You can now start building and validating your forms to ensure users enter data based on the expected format. HAPPY CODING!.