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!
Top comments (6)
While being a good academic excercise and a showcase of features, I hope that no one reading this will be enforcing these password rules (other than length) in production.
Password complexity requirements are a bad practice. You don't have to take me as an authority, quoting directly from NIST:
Exactly. If you really want to help the user show a calculated password strength and maybe - really only maybe - check it against a dictionary. But only to inform and educate. Don't enforce it.
or go one step further and offer passwordless forms of authentication
Thank you for the inputs, I very much appreciate the care for security and data protection!
Agree with the passwordless auth! Sometimes, though, using that is not possible. Some clients want their own user database or need to integrate with an existing login system. There may be several reasons for that. A bank, for example, surely has their own login system as to not expose who their clients are and to reduce the number of untrusted 3rd party integrations.
This entire post is indeed meant as an academic excercise to show Vue's reactivity and composition API as well as the multi-v-model feature. A password strength meter could be integrated rather quickly by applying a similar technique with specific rules (i.e.
plus 0.1 strength per special character added
) and the strength could be reported via v-model as well.Fully agree @thormeier and I understand that this is just a showcase :)
From my experience, passwordless authentication can be added to existing systems and you can still own all the data. If you refer to OAuth2-Logins with other providers, then it's a different case. But if you want to offer for email magic links or OTPs, then it can be added to an existing system. If you require higher levels of security, you can add WebAuthn / FIDO2 / passkey authentication, which offers 2FA by default and is very user-friendly!
Keep up the great work!