Boosting Email and Password Field UX with Vue.js

Before you dive in: I won't be going into every single line of code. Think of this as me sharing my personal notes and thoughts on how I'd go from an idea to code with you. If that sounds good, then let's go.

Form field UX and code coming together, eh?

Note: I won't be focusing on smooth animations this time, hence the jankiness.

Yep. This time I'll dive into two of the most "classic" elements of many applications, the humble email and password fields and "boost their UX" via the black magic of code.

I'll be sticking to a bunch of UX and Usability guidelines along the way, but I'll also throw in a few ideas of my own into the mix. Just for kicks.

UX and Usability Guidelines and ideas

I thought that laying out the guidelines I'll follow would be a great way to start out and also ensure having some kind of UX "base" to work from. There are a lot of recommendations when it comes to form fields and the following list isn't exhaustive.

Form fields in general

  1. Groups of related labels and fields should be clearly indicated visually.1
  2. Present fields in a single column layout to keep the user's flow through the form consistent.1
  3. Avoid placeholder text. 1 2
  4. Explain any input of formatting requirements. It's simple, just don't make your users play cat and mouse with a field's validation rules. 1
  5. Provide highly visible and specific error messages. We'll the Vue.js plugin "vee-validator" handle that. 1
  6. Labels should be top-aligned to ensure fastest completion times. You can do other alignments, sure, but we'll stick to top-aligned for this.3 4
  7. Don't validate immediately on input, but add a slight delay of 1 second as there is no reason to tell the user that they did something wrong just by typing one character.

Email fields

  1. Don't add an extra "Confirm email" field as it's redundant and adds extra noise for the user.5 No one likes repeating themselves unnecessarily and you can still write your email wrong twice, defeating the entire purpose of having two fields in the first place.
  2. Reduce the chance of mistyping common email provider domain names by automatically suggesting these domains as you type.
  3. Use one of the many email verification and validation APIs out there like mailboxlayer to do smart inline verification of emails. You can check for spam score, if there's an MX record, SMTP and even if it's a disposable email, so there's no excuse anymore.

Password fields

  1. Stop asking for a password confirmation, already.6 9 It reduces conversion7 and like any other confirmation field it does not work if the user types the same wrong input twice (or thrice etc.). Password fields without a "Show" option are even worse since you can't see if you wrote something wrong because they're MASKED.8
  2. Users should be able to see their password by clicking "Show" and hide it again by clicking "Hide".
  3. Users should be able to know how secure their passwords are using some sort of password strength indication.9
  4. Warn users that Caps Lock is on.10
  5. State the password requirements up front so it's always visible during typing.9

Phew...

That's a lot of recommendations and ideas. But we're not done before they have been transformed into actual working code.

Because, yeah, we can sit all day and talk about good UX, but at some point, some developer has to implement those guidelines so the end user can (hopefully) enjoy all the hard work that has been put into those years of research.

Technologies

I'll start by laying out the different tools I'll use when coding up these form fields:

  1. For the frontend "framework" part, I'll use Vue.js (version 2). More specifically the vue-cli that allows me to build everything using Vue's Single File Components (the .vue files) thanks to Webpack. Bear in mind, you could do all of this using Angular, React, jQuery, vanilla Javascript etc., it doesn't matter. Vue is just my preferred choice.

  2. While not a technology, per se, I will architecture all components using the "Atomic Design" approach by Brad Frost. You don't have to, of course. I just feel it fits perfectly with the Single File Component concept (with a few tweaks of my own).

  3. The CSS will be "functional" using the Basscss CSS toolkit instead of BEM or an object-oriented approach. I usually use BEM, but I'm loving this low-level approach so far. Just bear in mind that "functional CSS" is not some magic bullet. You're just moving kilobytes from CSS to JS (or to the rendered result), but it couples well with the whole component mindset that's emerging right now.

  4. I'll be using ES6 or ECMAScript 2016 as the preferred version of JavaScript. You should too, by the way. Also, I'll show you a neat little thing you can do with .repeat() when building the password strength meter.

  5. About that password strength meter. I'll be using zxcvbn, the password strength estimator by Dropbox to handle that part.

  6. For verifying emails, I'll be hooking up to mailboxlayer as mentioned before. You can use whatever service you want, but this is pretty simple. With Vue's vue-resource plugin for handling HTTP requests, it's even easier.

  7. Hiding the email provider suggestions list when clicking outside of the list is handled by the vue-clickaway directive. No reason to build that from scratch.

  8. For validating fields I use vee-validator. There are tons of validation solutions out there and this works fine. Pick whatever you fancy, but remember to use a validator that allows delaying the validation by a second or so.

Coding the field components using Vue.js

Be warned, there will be a lot of stuff to do in order to live up to these guidelines. Luckily, Vue.js and the other listed technologies make it easier to get there.

I will start with the email field and then move onto the password field afterward.

As I mentioned earlier, don't expect any of the following code to be production-ready or "beautiful". It won't. But it will show how you can implement some of the UX and Usability guidelines from earlier.

The <yui-email-field> component

I will start by taking a top-down approach, moving from the final result (or tag) and dive into the <yui-email-field> component itself shortly after.

First, this is the tag we will end up with:

<yui-email-field name="email"></yui-email-field>

This seemingly simple tag will then produce the following component:

Oh, right. I lied a little here, sorry.

It's actually two tags since I want the email field to be able to stand alone without any labels or help text, if necessary. Nevertheless, as we know from the guidelines, we should always have a field label present (among other things), so to accommodate for this we also have a <yui-field> tag that we can put our <yui-email-field> into. This then automatically wraps a label and a help text around the email field.

That part looks like this:

<yui-field name="email" help="This will be your username" label="Email">
  <yui-email-field name="email"></yui-email-field>
</yui-field>

Note A: The yui part of the tag is simply a namespace to keep the tag name from clashing with anything else that also might be named <email-field>.

Note B: It's probably possible to get the <yui-email-field> to inherit the name property of its parent <yui-field> component and avoid some redundancy. I might fix that in the future.

So, what's happening here?

A bunch of things is working in tandem under the hood. Let's break down what happens into features and then see where these features get implemented in the code, piece by piece.

  1. The <yui-email-field> component is made up of two custom Vue components; a <yui-input> component and a <yui-auto-suggester> component.

  2. When the user begins to write their email and types the @ symbol, a list of suggestions based on the most common email service providers appears. I handle this using a custom <yui-auto-suggester> component.

  3. The list will highlight (bold) parts of the suggested email domains as the user types.

  4. The user can move up and down the suggestions by pressing either the up or down key.

  5. By hitting the enter key, the currently selected suggestion will be chosen and appended after the @ symbol.

  6. The email will be validated 1 second after the user has stopped typing and not immediately on input. This is handled by vee-validate.

  7. After clicking outside of the field (on blur) we will attempt to verify the email using an email verification API. This time, it will be mailboxlayer. An unverified email will invalidate the email field.

The components behind <yui-email-field>

As described, the <yui-email-field> is made up of 2 Vue components. In Atomic Design terms, the <yui-input> is an Atom component while the <yui-auto-suggester> is a Molecule component because it too is made up of several custom components.

First, let's check out the <yui-input> component at the tag-level:

<yui-input
     type="email"
     :name="name"
     rules="required|email"
     :value="input"
     @input="handleInput"
     @enter="pickHighlightedSuggestion"
     @up="selectPreviousSuggestion"
     @down="selectNextSuggestion"
     @blur="verify"
     v-model="input"
     :bordered="bordered"
     icon="envelope">

And secondly, the <yui-auto-suggester> component:

<transition name="fade">
  <yui-auto-suggester 
   v-if="suggestionsExist && suggesting"
   :items="filteredSuggestions"
   :highlightable="domain"
   :active-item-index="activeSuggestionIndex"
   @click="use"
   @click-outside="suggesting = false"
   :class="[suggesterClasses]">
  </yui-auto-suggester>
</transition>

I won't go into details with the <yui-auto-suggester> component in this piece, but it could be a topic for a future article.

Note: The <transition> part is optional. It is simply one way to make the list of suggestions appear smoothly. You can learn more about it over at Vue's place.

Alright, let's go straight to the interesting stuff and see what happens at when the different events are triggered.

Triggering the list of suggestions when typing

In order to make the <yui-auto-suggester> appear, we first need to trigger it. One way of doing this is to look at what the user types and if there is a @ symbol (and only 1 symbol, of course) then tell the email field to show its suggestions.

To do this, I make use of a simple Regular Expression using the .match() method.

handleInput() {
  this.suggesting = (
    this.domain &&
    this.input.match(/\@/g) &&
    this.input.match(/\@/g).length === 1
  ) ? true : false
}

I also want to make sure that there's something being written on the right side of the @ symbol (the domain part). To do this I'm bringing in the computed property this.domain:

domain() {
  return this.input.split('@')[1]
}

If the conditions are met within the ternary operator, we'll set the this.suggesting data property to true and this will in return make the <yui-auto-suggester> appear with the list of domain suggestions defined within the data() part of the <yui-email-field> component:

data() {
  return {
    suggestions: [
     'aol.com',
     'facebook.com',
     'gmail.com',
     'googlemail.com',
     'google.com',
      // ... etc
   ]
  }
}

Filtering through the suggestions as the user types

With the list now appearing, let's make it a little smarter. You've probably experienced how auto-completion works when typing in Google's search field or similar fields. You type something in the field and as you type the list is automatically reduced.

We can do the same thing by combining the ES6 method .filter() and the good old .match() method in a computed property. Like this:

filteredSuggestions() {
  return this.suggestions.filter((suggestion) => {
   return this.suggesting ? suggestion.match(this.domain) : suggestion
  })
}

Remember, this.domain is also a computed property that works in tandem with whatever the user types (the this.input). The cool thing here is that by filtering the suggestions based on whatever suggestion that matches this.domain, we can achieve this automatic reduction quite easily.

The list of suggestions is reduced as the user types, leaving behind the suggestions that match the input the most. Simple.

Cycling through the active suggestion using up and down keys

The list appears to filter its suggestions automatically; awesome. Next up is to go through the active selection using the up and down keys.

This is handled in a pretty straightforward manner by incrementing and decrementing the suggestion index number whenever either key is pressed. Like this:

selectPreviousSuggestion() {
  if (this.activeSuggestionIndex > 0) this.activeSuggestionIndex--
 },
 selectNextSuggestion() {
  if (this.activeSuggestionIndex < this.filteredSuggestions.length - 1) this.activeSuggestionIndex++
}

As the index number goes up or down, the currently highlighted selection changes as seen in the video earlier.

Validating and verifying the email

So far I've shown you how suggestions are triggered, filtered and navigated. While that part aims to reduce the mistyping of common email domains and reduce the amount of typing needed, validating and verifying the email is handled by something else.

Let's take a look at that.

Validation using vee-validator

First, handling validation is actually quite simple and not something that the <yui-email-field> is responsible for. I delegate that task to the more general <yui-input> Atom component, which is connected to the vee-validator plugin.

You can use whatever validator you want, but in this case vee-validator works like a charm. Import it using yarn or npm, follow the docs and you're off to the races. In the case of my <yui-input> it'll be in the form of data-vv and v-validate attributes that I add to the core <input> DOM element:

<input
 ...other properties
 :data-vv-rules="rules"
 :data-vv-value-path="path"
 :data-vv-name="name"
 :data-vv-delay="validationDelay"
 v-validate
 autocomplete="off">

vee-validator has a good amount of validation rules already built in, which you can dive into here. In this case I only need the rules required|email as seen here in the <yui-email-field> component:

<yui-input
 type="email"
 :name="name"
 rules="required|email"
 ... other properties>

Of course, the required part could be omitted, but the sake of showing some different error situations I included it.

Verifying using the mailboxlayer API

Similar to validation, verifying the user's email is straightforward when using an external API such as mailboxlayer. Note that you typically only get a limited amount of requests on free plans for such API services.

One way of implementing this into your email field component is to try and verify the email when the user leaves the field (e.g. on blur) and then invalidate the field if the verification fails whatever rules you've set up.

Such a verification method could look like this (which would go into your methods part of your Vue component):

verify() {
  /* Remember kids, never put your API keys directly into your production code. This is just for demonstration purposes.*/
  const emailVerificationServiceAPIKey = `9fa5c37Xa33a92d23e5Y4037bcZ3d2Yb`

  this.verifying = true

  this.$http
  .get(`http://apilayer.net/api/check?access_key=${emailVerificationServiceAPIKey}&email=${this.email}&smtp=1&format=1`)
  .then((response) => {
     const email = response.body

     this.verified = (
       email.score > 0.5 &&
       email.mx_found &&
       email.smtp_check &&
       !email.disposable &&
       email.format_valid
     ) ? true : false

     if (!this.verified) {
       bus.$emit(`${this.name}_valid`, false)
       bus.$emit(`${this.name}_invalid`, true)
     }

   }).then(() => {
        this.verifying = false
        this.verificationDone = true
      })
    }

Again, nothing's super complicated here. We call the API using vue-resource's $http.get() method and put in the given email (this.email) to get it verified. If it lives up to our demands, great, then it's verified and things go green. If not, then too bad and we $emit a signal to the field telling it to turn invalid.

One cool thing about these verification APIs is that we can fine-tune how much a given email should live up to stuff such as the quality and deliverability of the email (the score), if it's a disposable throwaway email (the disposable), if the SMTP check succeeded, if there are MX records and also if the email format itself is valid. You could check for more stuff, but you get the idea.

The <yui-password-field> component

So, we've got the email field in place auto-suggesting domain endings and merrily verifying emails. Let's move on to the password field component and zoom in on the following parts:

  1. Showing and hiding the password mask.
  2. Caps Lock detection and informing the user about it.
  3. Handling password strength estimation.

Or as shown in this video...

Showing and hiding the password mask

This is actually pretty straightforward in almost any JavaScript framework or even in plain vanilla JS. Often it boils down to switching the type of the input field from password to text (and back again!) when the user clicks a button labeled "Show" or "Hide".

In Vue.js, a combination of ternary operators and toggling a Boolean value directly in the template, as shown below, will do fine. Of course, you could create individuals methods for doing so, but in such a simple example it's not worth it.

<yui-input
 :type="masking ? 'password' : 'text'"
 :name="name"
 :value="password"
 v-model="password"
 @blur="masking = true">

 <yui-button
 @click="masking = !masking"
 v-if="password"
 v-text="masking ? 'Show' : 'Hide'">
 </yui-button>

So, what's going on here inside the <yui-password-field> component? As you might have noticed it consists of two individual Atoms (<yui-input> and <yui-button>). By switching :type attribute of the input component when the masking value changes it makes it quite easy to display and hide the user's password.

This is then connected to the button's @click event, which simply sets the value of masking to whatever it is not, e.g. the opposite, enabling the toggling.

Note: As an extra safety measure, I mask the password on @blur when the user clicks outside the field. This is for situations where the user has clicked "Show", but then forgot to hide the password afterward.

Caps Lock detection

Among the minor annoyances when typing a password (especially when it's masked) is to suddenly realize that you've had Caps Lock on all the time.

We can improve this experience by telling the user that Caps Lock is in fact on when the interact with the field. To implement such a feature used to be a little tedious in older browsers, but checking for Caps Lock can now be done in a single line of code:

this.capsLockOn = event.getModifierState && event.getModifierState('CapsLock')

I do this in the core <yui-input> component, inside a method that runs when the user presses a key down (the @keydown event) and then $emit the event in order to use it later in other components:

onKeydown(event) {
  this.capsLockOn = event.getModifierState && event.getModifierState('CapsLock')
  bus.$emit(`${this.name}_capsLockOn`, this.capsLockOn)
}

This 'CapsLock' event is then sent through the system (via an event bus) and captured in the surrounding <yui-field> component:

<template>
  <div :class="[baseClasses, stateClasses, aestheticClasses]">
    <label :class="[labelClasses]" :for="name">
      <div class="flex justify-between">
        <span v-text="label">
      </div>
      <div v-if="help" class="h4 gray" v-text="help"></div>
     <div v-if="detectCapsLock && capsLockOn">Your Caps Lock key is on
    </label>
    <slot>
  </div>
</template>

Detecting the Caps Lock event is also done by the event bus and can be placed in the mounted() part of the component life cycle:

mounted() {
  // ... other events
  bus.$on(`${this.name}_capsLockOn`, (value) => this.capsLockOn = value)
}

The detectCapsLock is a prop because we don't necessarily want every single input field to show a Caps Lock message.

Password strength estimation with zxcvbn

Helping the user to a safer password is also quite straightforward; bring in Dropbox's zxcvbn library, hook it up to the password field as the user types and display a proper message.

It can be as simple as this:

computed: {
  estimation() {
    return zxcvbn(this.password)
  },
  strength() {
    return this.password ? this.estimation.score : null
  },
  strengthClasses() {
    if (this.strength == 1) return `red`
    if (this.strength < 4) return `orange`
    if (this.strength == 4) return `green`
  },
  strengthLabel() {
    if (this.strength < 1) return `very easy to hack`
    if (this.strength == 1) return `easy to hack`
    if (this.strength == 2) return `fairly safe`
    if (this.strength == 3) return `quite safe`
    if (this.strength == 4) return `extremely safe`
  },
}

But there's a little gotcha. Dropbox recommends loading it in via a CDN, but since the zxcvbn is a large library to load in, you should do it asynchronously. That way your users won't be sitting forever waiting for your application to load.

With that in place, all we have to do is show the text:

<span>Your password is {{ strengthLabel }}.</span>

zxcvbn takes care of the rest.

Bonus: a little ES6 trick with .repeat()

If you want to show a graphical representation of the user's password strength you can make use of the .score value that zxcvbn returns after estimating the password.

The way I did it was to use the new ES6 String method called .repeat() and used it on a simple bullet Unicode character (). I then put in the .score as the times it should repeat the character and presto; a simple visualization of how strong the password is.

<span>{{ `•`.repeat(strength) }}</span>

That's it, folks

A lot goes into improving the user's experience when interacting with something as simple as form fields, but I every little step matters. Many of the UX and usability guidelines are actually quite straightforward to code and implement, so I hope you will be inspired to do so in your upcoming (or existing) web project.

Did I miss something? Got suggestions for improving this piece? Did I mess up somewhere? Feel free to let me know and I'll look into it.


References

  1. Whitenton, Kathryn (from NNGroup), 2016: "Website Forms Usability: Top 10 Recommendations"

  2. Sherwin, Katie (from NNGroup), 2014: "Placeholders in Form Fields Are Harmful"

  3. Penzo, Matteo, 2006: "Label Placement in Forms"

  4. Jarett, Caroline, 2013: "Don’t Put Labels Inside Text Boxes"

  5. Wroblewski, Luke, 2009: "Email Entry in Web Forms"

  6. Tseng, Anthony, 2015: "Why the confirm password field must die"

  7. New, Tom, 2004: "Small changes lead to a 55% increase in conversion"

  8. Nielsen, Jakob (from NNGroup), 2009: "Stop Password Masking"

  9. Sherwin, Katie, 2015 (from NNGroup): "Password Creation: 3 Ways To Make It Easier"

  10. Babich, Nick, 2016 (from UX Planet): "Designing UX Login Forms and Process"