Vue 3 has brought us a ton of fancy new features. The composition API lets us create components in a more intuitive, JS-y way, there is now support for multiple v-model
directives, full TypesSript support out of the box, and the reactivity improved a lot when compared to Vue2.
Today, we'll put these things to use to create a fancy password input component: One that lets us define password requirements and informs the outer component and the user about the password validity and the password.
The basis
First, we create a new Vue component called PasswordInput.vue
. In there, we add some template, an amepty script tag and some styling:
<template>
<div class="password-input">
<label for="password">
Enter password
</label>
<!-- We'll add stuff to this input later -->
<input
type="password"
id="password"
>
<button>
Show password
</button>
<label for="password-repeat">
Repeat password
</label>
<!-- We'll add stuff to this input later -->
<input
type="password"
id="password-repeat"
>
<ul class="requirements">
<!-- Add requirements here -->
</ul>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
.password-input {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 8px;
font-size: 16px;
}
input {
margin-bottom: 12px;
padding: 8px;
font-size: 16px;
}
button {
margin-bottom: 12px;
}
.requirements {
font-weight: bold;
}
.is-success {
color: #96CA2D;
}
.is-error {
color: #BA3637;
}
</style>
We added two input fields for password and password repeat, a button that will show and hide the password as well as an unordered list for the password requirements that we'll add later.
Notice how we're using <script setup lang="ts">
to tell Vue that we want to use the composition API for the entire component.
The styling already includes classes for success and failure. The rest is rather rudimentary. When we use the component, we should see something like this:
Hooking up the fields
Let's add two refs for the password and password repeat. We also add a flag to determine if the password is currently shown or not. While we're at it, we also import computed
and watch
, because we'll use that later. For that, we add the following to the script tag:
import { computed, ref, watch } from 'vue'
const password = ref('')
const passwordRepeat = ref('')
const showPassword = ref(false)
We can now hook these up with the template:
<!-- ... -->
<label for="password">
Enter password
</label>
<input
:type="showPassword ? 'text' : 'password'"
v-model="password"
@change="$emit('update:password', $event.target.value)"
id="password"
>
<button @click="showPassword = !showPassword">
{{ showPassword ? 'Hide password' : 'Show password' }}
</button>
<label for="password-repeat">
Repeat password
</label>
<input
:type="showPassword ? 'text' : 'password'"
v-model="passwordRepeat"
id="password-repeat"
>
<!-- ... -->
Adding the requirements
Now comes the fun part. We want to validate the password with the following requirements:
- It should be at least 8 characters long
- It should contain at least one uppercase letter
- It should contain at least one lowercase letter
- It should contain at least one symbol
- It should contain at least one number
- It must match the "Repeat password" field
To get Vue's reactivity in, we can create a computed value that checks the password
ref for all fo these:
const passwordRequirements = computed(() => ([
{
name: 'Must contain uppercase letters',
predicate: password.value.toLowerCase() !== password.value,
},
{
name: 'Must contain lowercase letters',
predicate: password.value.toUpperCase() !== password.value,
},
{
name: 'Must contain numbers',
predicate: /\d/.test(password.value),
},
{
name: 'Must contain symbols',
predicate: /\W/.test(password.value),
},
{
name: 'Must be at least 8 characters long',
predicate: password.value.length >= 8,
},
{
name: 'Must match',
predicate: password.value === passwordRepeat.value,
}
]))
Using an array of requirements has several advantages:
- We can extend it whenever we like
- Rendering it is trivial with a
v-for
loop - We can reuse the predicates and move them to a utils file or use them in a state management system
Let's render the requirements in the list:
<!-- ... -->
<ul class="requirements">
<li
v-for="(requirement, key) in passwordRequirements"
:key="key"
:class="requirement.predicate ? 'is-success' : 'is-error'"
>
{{ requirement.name }}
</li>
</ul>
<!-- ... -->
Now, whenever the user changes either one of the values, the requirements are recalculated and updated in the template, giving feedback to the user directly. We should now see something like this:
So far so good!
Emitting changes and usage
We now want to make the component usable. For that, we need to emit changes. Since Vue now allows to have multiple v-model
directives on a component, we can emit different events in case of changes to either password field. For that, we add @change
event listeners to the password input fields:
<!-- ... -->
<input
:type="showPassword ? 'text' : 'password'"
v-model="password"
@change="$emit('update:password', $event.target.value)"
id="password"
>
<!-- ... -->
<input
:type="showPassword ? 'text' : 'password'"
v-model="passwordRepeat"
@change="$emit('update:passwordRepeat', $event.target.value)"
id="password-repeat"
>
<!-- ... -->
We then also want to emit if the password is valid or not, so a single boolean value. We use a watch for that, that will watch the passwordRequirements
compured value and emit a single boolean.
const emit = defineEmits([
'update:password',
'update:passwordRepeat',
'update:validity'
])
watch(passwordRequirements, () => {
emit(
'update:validity',
passwordRequirements.value.reduce((v, p) => v && p.predicate, true)
)
})
And done! The component now emits changes for the password, the password-repeat field, and the password validity. It also shows the user direct visual feedback about the password's validity and let's them show or hide the password to see if they perhaps mistyped.
We can now use the component as follows in any template:
<template>
<!-- ... -->
<PasswordInput
v-model:password="password"
v-model:password-repeat="passwordRepeat"
v-model:validity="isPasswordValid"
/>
<!-- ... -->
</template>
Awesome!
💡 Want to learn more about Vue and its advanced features? I recently published a course on Educative.io called "Advanced VueJS: Build Better Applications Efficiently"!
In 51 lessons, you can learn about all kinds of features, such as custom events, advanced templating, how to use APIs, I18n or slots and hooks.
You can find the course over on Educative.io!
I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a ❤️! I write tech articles in my free time and like to drink coffee every once in a while.
If you want to support my efforts, you can offer me a coffee ☕ or follow me on Twitter 🐦! You can also support me directly via Paypal!