Use ALL the Features: How To Create a Fancy Password Input With Vue3 🔑✅
Tags: vue, webdev, tutorial, javascript
Published: 2023-04-01 10:50:39

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:

```vue
<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:

![Two input fields with labels and a button.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p10t5ou27lglwgz09u4q.png)

## 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:

```typescript
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:

```vue
<!-- ... -->
    <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:

```typescript
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:

 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:

```vue
<!-- ... -->
    <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:

![The same password input as above, but now with a list of fulfilled and unfulfilled requirements](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xoxj0d168456b7c26pp.png)

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:

```vue
<!-- ... -->
    <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.

```typescript
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:

```vue
<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](https://www.educative.io/courses/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](https://www.educative.io/courses/advanced-vuejs-build-better-applications-efficiently)!

---

*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* ☕](http://buymeacoffee.com/pthormeier) *or* [*follow me on Twitter* 🐦](https://twitter.com/pthormeier)*!* *You can also support me directly via [Paypal](https://www.paypal.me/pthormeier)!*

[![Buy me a coffee button](https://dev-to-uploads.s3.amazonaws.com/i/x55hp6jopwyy161d8e2u.png)](http://buymeacoffee.com/pthormeier)