There are plenty of opportunities for friction in the user experience when logging in, particularly while entering a two factor authentication code. As developers we should be building applications that support the need for account security but don’t detract from the user experience. Sometimes it can feel as though these requirements are in a battle against each other.

In this post we will look at the humble element and the HTML attributes that will help speed up our users’ two factor authentication experience.

The default experience

When you implement two factor authentication for a web application, perhaps with the Authy two factor authentication API, you will need a form for your user to input the one time password you are going to send them. You might create something similar to the following HTML:

<form action="" method="POST">
    <label for="token">Please enter the code you were sent:label>
    <input type="text" name="token" id="token" />
  <button type="submit">Check tokenbutton>

This is a good experience already. The has a name and a unique ID and the is using the correct for attribute to explicitly associate with it, which is important for accessibility. You could also wrap the around the for the same effect. With a bit of CSS, this might look something like this:

A web page shown in iOS Safari with a two factor authentication prompt. The standard alphabetical keyboard is open.

However we can progressively enhance this experience with just a few more attributes.

Getting the right keyboard

On mobiles or devices with on-screen keyboards the first thing to notice is that we are presenting the full alpha-keyboard. One time passwords are made of numerical characters so it would be much better to present the user with a number pad.

You might think that switching the type of from “text” to “number” is the solution here:

    <input type="number" name="token" id="token" />

You would be wrong though. This does trigger a different keyboard on iOS, but it still includes a number of useless keys.

A web page shown in iOS Safari with a two factor authentication prompt. This time the keyboard includes numbers and symbols.

Changing the type of field changes the way the browser interprets that field. It could cause errors too; if the two factor authentication code starts with a zero a number field may drop that leading zero.


The inputmode attribute changes the keyboard the browser should display without changing the meaning of the data the field collects. We want our to receive text input, but from the numeric keyboard. So instead, add inputmode="numeric"

    <input type="text" name="token" id="token" inputmode="numeric" />

inputmode has a number of other values, including “tel” for telephone numbers, “email”, “decimal”, “url”, “search” and “none” in case you want to render your own keyboard. This article on CSS Tricks has all the details you need for the different inputmodes.

Browser support for inputmode is good for mobile operating systems these days, but a couple of years ago it was in the wilderness. For older browsers there is another trick to trigger the numeric keyboard and include a bit of extra validation for free.


The pattern attribute allows you to validate the contents of an using a regular expression. Using the pattern [0-9]* tells the browser that we only accept numbers in the field and also triggers the number pad in browsers without inputmode support.

Our HTML looks like this now:


And the keyboard is a much simpler numerical input:

A web page shown in iOS Safari with a two factor authentication prompt. This time a simple keyboard of just numbers appears.

One thing that would make this form even easier to fill would be autocompletion of the one time password.

HTML autocomplete

According to MDN “autocomplete lets web developers specify what if any permission the user agent has to provide automated assistance in filling out form field values, as well as guidance to the browser as to the type of information expected in the field.”

In iOS and Safari on macOS we can take advantage of this to have the browser suggest two factor authentication codes that are sent to the device over SMS. Adding the autocomplete attribute with the value “one-time-code” will trigger this behaviour.


With this, our is complete and the experience for the user now looks like this:

A web page shown in iOS Safari with a two factor authentication prompt. There is also a message notification with a two factor authentication code and the keyboard is auto suggesting the code to be filled in.

Other useful autocomplete values

There are many autocomplete values available, covering everything from names and addresses to credit cards and other account details. For sign up and login there are a few autocomplete values that stand out as useful hints: username, email, new-password, current-password.

Browsers and password managers have very good heuristics to find login forms on web pages, but using the username and current-password values make it very obvious. You definitely want to consider using these attributes if you are building a login form with the username and password on different pages.

In a sign up form, make sure to use the “new-password” value as it triggers password suggestions in some browsers.

A sign in form in Firefox. The password field has suggested a strong password.

Autocompleting one time passwords in other browsers

This autocomplete behaviour only currently exists in Safari on iOS and macOS, but the Chrome team is investigating similar ideas to streamline this process. Currently there is an experiment with an imperative SMS Receiver API modelled on Android’s SMS receiver API. This would allow developers to extract the one time password from the SMS and, because it’s in JavaScript, instantly submit the form, saving the user more time.

At the time of writing this API is part of an origin trial which allows you to test it out and feed back to the Chrome team. If this interests you, sign up and give the API a whirl.

Better experiences through HTML

In this post we have seen that with just a sprinkling of HTML attributes we can improve the login experience for our users, particularly on mobile devices.

The element is one of the most interesting HTML elements we have access to. Depending on the attributes it can be a text field, a range slider, a file selector a button and many more.

If you are looking for an even better two factor authentication experience, then take a look at Authy push authentication which cuts out the copying of codes entirely.

Do you have any other tips on improving the login experience for your users? I’d love to hear them in the comments below or hit me up on Twitter at @philnash.


The DOM is just a little weird about some things, and the way you deal with attributes is no exception. There are a number of ways to deal with the attributes on elements. By attributes, I mean things like the id in

. Sometimes you need to set them. Sometimes you need to get them. Sometimes you get fancy helpers. Sometimes you don’t.

For this article, I’ll assume el is a DOM element in your JavaScript. Let’s say you’ve done something like const el = document.querySelector("#cool"); and matched

or whatever.

Some attributes are also attributes of the DOM object itself, so iff you need to set an id or title, you can do:; // "cool"
el.title = "my title";
el.title; // "my title";

Others that work like that are lang, align, and all the big events, like onclick.

Then there are attributes that work similarly to that but are nested deeper. The style attribute is like that. If you log you’ll see a ton of CSS style declarations. You can get and set them easily: = "red"; = "black";

You can get computed colors this way too. If you do hoping to get the color of an element out of the gate, you probably won’t get it. For that, you’d have to do:

let style = window.getComputedStyle(el);
style.color; // whatever in CSS won out

But not all attributes are like first-class attributes like that.

el['aria-hidden'] = true; // nope

That “works” in that it sets that as a property, but it doesn’t set it in the DOM the proper way. Instead, you’ll have to use the generic setter and getter functions that work for all attributes, like:

el.setAttribute("aria-hidden", true);

Some attributes have fancy helpers. The most fancy is classList for class attributes. On an element like:

You’d have:

el.classList.value; // "module big"
el.classList.length; // 2
el.classList.add("cool"); // adds the class "cool", so "module big cool"
el.classList.remove("big"); // removes "big", so "module cool"
el.classList.toggle("big"); // adds "big" back, because it was missing (goes back and forth)
el.classList.contains("module"); // true

There’s even more, and classList itself behaves like an array so you can forEach it and such. That’s a pretty strong reason to use classes, as the DOM API around them is so handy.

Another attribute type that has a somewhat fancy help is data-*. Say you’ve got:


You’ve got dataset:

  active: "true",
  "placement", "top right"
*/; // "true"
el.dataset.extraWords; // "hi", note the conversion to camelCase = "false"; // setters work like this