Implementing Cross‑Field Password Confirmation in VeeValidate 4 for Vue 3
Learn how to add a reusable matches rule in VeeValidate 4 to validate password confirmation fields without extra watchers.
22 Jul 2025, 01:39 UTC

The problem: password fields that must match
When building a sign‑up form you often need to ensure the “Confirm password” field contains exactly the same value as the “Password” field. Doing this with VeeValidate 4 can feel tricky because the validation rule lives on a single field but needs to look at another field’s value.
Thesis: a reusable custom rule solves it
By registering a global rule named matches that receives a CSS selector, you can declaratively tie the confirmation field to the password field without writing extra watchers or computed properties.
How to add the rule
- Install VeeValidate 4 (if not already):
npm i veevalidate@^4 - In your app’s entry file (e.g.,
main.jsormain.ts), importextendand define the rule:
import { extend } from 'vee-validate';// matches rule: value must equal the value of the element pointed to by selectorextend('matches', { // params: [selector] – e.g. '#password' validate: (value, [selector]) => { const target = document.querySelector(selector) as HTMLInputElement; return target ? value === target.value : false; }, message: 'The passwords do not match.'}); The rule receives the current field’s value as value and an array of parameters; we expect the first parameter to be a selector string that points to the source field.
Using the rule in a form
Below is a minimal sign‑up component that uses the ValidationProvider wrapper from VeeValidate.
<template> <ValidationObserver ref='observer' v-slot='{ invalid }'> <form @submit.prevent='onSubmit'> <ValidationProvider name='password' rules='required|min:6' v-slot='{ errors }'> <label>Password</label> <input type='password' v-model='password' /> <span>{{ errors[0] }}</span> </ValidationProvider> <ValidationProvider name='confirmPassword' rules='required|matches:#password' v-slot='{ errors }'> <label>Confirm Password</label> <input type='password' v-model='confirm' /> <span>{{ errors[0] }}</span> </ValidationProvider> <button type='submit' :disabled='invalid'>Sign up</button> </form> </ValidationObserver></template> <script>import { ValidationObserver, ValidationProvider } from 'vee-validate';import { ref } from 'vue';export default { components: { ValidationObserver, ValidationProvider }, setup() { const password = ref(''); const confirm = ref(''); const observer = ref(null); const onSubmit = async () => { const valid = await observer.value.validate(); if (valid) { alert('Form is valid!'); } }; return { password, confirm, observer, onSubmit }; }};</script> <style scoped>input { display: block; margin: 0.5rem 0; }span { color: red; font-size: 0.875rem; }</style></code></pre> Notice how the confirm‑password provider declares rules='required|matches:#password'. The selector #password points to the password input’s id, which we add implicitly via Vue’s two‑way binding (you can also add to the input if you prefer).
Trade‑off and limitation
- The rule relies on a DOM query (
document.querySelector) to read the sibling field’s value. This creates a small coupling to the template structure: if the target field is conditionally rendered with v-if or hidden with v-show at the moment the rule runs, the query returns null and the validation will fail incorrectly. - To mitigate, ensure the target field is always rendered (e.g., place it outside conditional blocks) or pass a
ref instead of a selector and read ref.value directly.
You can verify the rule works by opening the browser’s DevTools, selecting the <ValidationObserver /> component, and checking its errors array after each keystroke. The error for the confirm field should appear only when both fields have values and they differ.
Actionable closing
Add the matches rule once, reuse it across any form that needs field‑to‑field equality (password confirmation, email repeat, etc.). Keep the selector simple and stable, and test the form with both matching and mismatching values to confirm the error appears and disappears as expected. This keeps your validation logic declarative, easy to read, and free of extra watchers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.