DEV Community

Cover image for Use ALL the Features: How To Create a Fancy Password Input With Vue3 πŸ”‘βœ…
Pascal Thormeier
Pascal Thormeier

Posted on

Use ALL the Features: How To Create a Fancy Password Input With Vue3 πŸ”‘βœ…

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>


Enter fullscreen mode Exit fullscreen mode

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:

Two input fields with labels and a button.

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)


Enter fullscreen mode Exit fullscreen mode

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"
    >
<!-- ... -->


Enter fullscreen mode Exit fullscreen mode

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,
  }
]))


Enter fullscreen mode Exit fullscreen mode

Using an array of requirements has several advantages:

  1. We can extend it whenever we like
  2. Rendering it is trivial with a v-for loop
  3. 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>
<!-- ... -->


Enter fullscreen mode Exit fullscreen mode

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:

The same password input as above, but now with a list of fulfilled and unfulfilled requirements

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"
    >
<!-- ... -->


Enter fullscreen mode Exit fullscreen mode

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)
  )
})


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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!

Buy me a coffee button

Top comments (6)

Collapse
 
rytis profile image
Rytis

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:

Research has shown, however, that users respond in very predictable ways to the requirements imposed by composition rules. For example, a user that might have chosen β€œpassword” as their password would be relatively likely to choose β€œPassword1” if required to include an uppercase letter and a number, or β€œPassword1!” if a symbol is also required.

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

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.

Collapse
 
vdelitz profile image
vdelitz • Edited

or go one step further and offer passwordless forms of authentication

Thread Thread
 
thormeier profile image
Pascal Thormeier

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.

Thread Thread
 
vdelitz profile image
vdelitz

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!

Collapse
 
Sloan, the sloth mascot
Comment deleted