4

Using a Single Input for one-time-code

 1 year ago
source link: https://dev.to/madsstoumann/using-a-single-input-for-one-time-code-352l
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Cover image for Using a Single Input for one-time-code
Mads Stoumann

Posted on Jun 12

33 5 2 2 4

Using a Single Input for one-time-code

The other night I had a bit of fun trying to create a single-input one-time-code:

One-Time-Code

one-time-code is a valid autocomplete-value, and with a few lines of JS, it let's you fill out the field from a text-message (sms).

More often, it's called OTP, though.

From Wikipedia:

A one-time password (OTP), also known as a one-time PIN, one-time authorization code (OTAC) or dynamic password, is a password that is valid for only one login session or transaction, on a computer system or other digital device.


Back to my example.

It's using very simple markup:

<input
  type="text"
  autocomplete="one-time-code"
  inputmode="numeric"
  maxlength="6"
  pattern="\d{6}"
>

The CSS is a bit more complex:

:where([autocomplete=one-time-code]) {
  --otp-digits: 6; /* length */
  --otc-ls: 2ch;
  --otc-gap: 1.25;
  /* private consts */
  --_otp-bgsz: calc(var(--otc-ls) + 1ch);

  all: unset;
  background: linear-gradient(90deg, 
    var(--otc-bg, #EEE) calc(var(--otc-gap) * var(--otc-ls)),
    transparent 0
  ) 0 0 / var(--_otp-bgsz) 100%;
  caret-color: var(--otc-cc, #333);
  clip-path: inset(0% calc(var(--otc-ls) / 2) 0% 0%);
  font-family: ui-monospace, monospace;
  font-size: var(--otc-fz, 2.5em);
  inline-size: calc(var(--otc-digits) * var(--_otp-bgsz));
  letter-spacing: var(--otc-ls);
  padding-block: var(--otc-pb, 1ch);
  padding-inline-start: calc(((var(--otc-ls) - 1ch) / 2) * var(--otc-gap));
}

It's a bunch of stuff simulating 6 fields (from the property --otc-digits), while — in reality — it's just a single <input>. The spacing between the "fields" is due to letter-spacing, and the gray "boxes" are from a linear-gradient.

It has to use a monospace-font, so the magic value of 1ch works — same applies to the letter-spacing. 1ch equals the width of a zero.


But why?

Have you ever created a OTP-component before?

I'm writing "component", because it's typically a <fieldset> with six <input>s and a bunch of JavaScript to detect when you enter- or leave a field etc.

When you fill out the field from the Web OTP API, you need to split the value, and fill-in six fields instead of one.

With a single input it's much simpler:

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);

Highlightning the current "field"

The single-input OTP is not perfect. When you move from "field" to "field", it would be a better user-experience, if the caret was a block:

.selector {
  caret-shape: block;
}

Unfortunately, no browsers support caret-shape yet.


Another way, would be to add another background-gradient, but without repeating the pattern:

Image description

And position it by multiplying the size-property — --_otp-bgsz — with the digit-number, --_otp-digit, as a custom property:

.selector {
  background-position: 
    calc(var(--_otp-digit, 0) * var(--_otp-bgsz)) 0;
}

This is not perfect, because we'd need to put the digit in a CSS Custom Property, and then update that with JavaScript:

input.addEventListener('input', () => 
  input.style.setProperty('--_otp-digit', 
  input.selectionStart)
)

Could this be done in an even simpler way? Other sugestions? Please use the comments!


Here's a Codepen-demo:


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK