close
tony / OTP Input

One Time Password Input

There's surprisingly not a great out of the box solution to this that has all of the following:

  • Support for borders around each digit.
  • Support for auto populating from SMS
  • Support for copy/paste
  • Support for moving to the next input after each digit
  • Support for using the arrow keys to move back and forth

Approach 1

This is the simplest approach and should be the go-to. Because it's a single input it supports everything natively, except for borders around each of the digits. It shows the number input on mobile, ensures there's only 4 characters (swap to 6 if necessary), autofocuses the input, checks for numericality, and auto-fills from SMS.

Code

input[autocomplete=one-time-code] {
  border-bottom: 2px solid #f6f6f7;
  width: 240px;
  letter-spacing: .5ch;
  font-size: 60px;
  font-family: monospace;
  font-weight: bold;
}
input[autocomplete=one-time-code]:focus {
  outline: none;
  border-bottom: 2px solid #191919;
}
<form>
  <label for="otp" class="block" >Enter OTP</label>
  <input id="otp" autofocus="true" placeholder="0000" required class="no-style" autocomplete="one-time-code" type="text" inputmode="numeric" maxlength="4" pattern="\d{4}" />
</form>

Approach 2

This approach is from Chris Coyier's blog post. This approach uses a single input, with a background pattern that gives the impression of multiple inputs. The downside is the "dangling" cursor at the end. If I had more time I'd solve this by either unfocusing the input with js after 4 characters, or by fixing the width and using overflow hidden to hide it.

Code

input.bordered[autocomplete="one-time-code"] {
  --magic-number: 100px;
  padding-left: 30px;
  width: 500px;
  overflow: hidden;
  background-image: linear-gradient(#fff, #fff),
    url("https://assets.codepen.io/3/rounded-rectangle.svg");
  background-size: var(--magic-number);
  background-position-x: right, left;
  background-repeat: no-repeat, repeat-x;
  border: 0;
  height: var(--magic-number);
  font-size: calc(0.6 * var(--magic-number));
  font-family: monospace;
  letter-spacing: calc(0.64 * var(--magic-number));
  box-sizing: border-box;
  overflow: hidden;
}
input.bordered[autocomplete="one-time-code"]:focus {
  outline: none;
  background-image: linear-gradient(#fff, #fff),
    url("https://assets.codepen.io/729148/blue-rounded-rectangle.svg");
  background-size: var(--magic-number);
  background-position-x: right, left;
  background-repeat: no-repeat, repeat-x;
} 
<form class="pb-8">
  <label for="otp2" class="block" >Enter OTP</label>
  <input id="otp2" autofocus="true" placeholder="0000" required class="no-style bordered" autocomplete="one-time-code" type="text" inputmode="numeric" maxlength="4" pattern="\d{4}" />
</form>

Approach 3

Leaving this here as a placeholder for when I have time to write a version with multiple inputs that uses Mini.js for interactivity.


References

Add this to the <head> of your page