Skip to content

Components

React OTP Inputs — One Input Wearing Six Boxes, Not Six Inputs Fighting You

Paste Works, Backspace Works, iOS Autofill Works — Because There's Only One Real Input
Written by Aiden Wood Founder and Lead Design Engineer
🔢 📋 🔐

Every OTP screen looks the same: six tidy boxes. The naive build is six <input> elements with focus-juggling JavaScript — and it collapses the moment someone pastes a code, holds backspace, or expects the SMS autofill bar to work. Which is everyone.

Velocity X's otp-code-input uses the model that survives contact with real users: one real input, six painted boxes.

The Money Pattern

A single invisible input holds the whole value; the boxes are a visualisation of its characters:

<div class="relative" onClick={() => ref.current?.focus()}>
  <input
    ref={ref}
    value={code}
    onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
    inputMode="numeric"
    autoComplete="one-time-code"
    class="absolute inset-0 opacity-0"
  />
  <div class="flex gap-2" aria-hidden="true">
    {Array.from({ length: 6 }, (_, i) => (
      <div class={i === code.length ? 'ring-2' : ''}>{code[i] ?? ''}</div>
    ))}
  </div>
</div>

Paste just works — it's a normal input receiving six characters. Backspace just works. Selection, cursor position, IME input: all the behaviour browsers spent 25 years getting right, inherited instead of re-implemented. The "cursor" is the box at code.length getting a ring.

The Catch

Two attributes do more than all the JavaScript. autoComplete="one-time-code" is what makes iOS offer the code straight off the SMS — miss it and your users are memorising six digits across an app switch. inputMode="numeric" gets the number pad on mobile without type="number"'s spinner baggage.

And because the real input is visually hidden, it still has to be a real label target — the boxes are aria-hidden decoration, the input carries the accessible name. A screen reader user gets one sensible "6-digit code" field, not six mystery single-character inputs.

The Verdict

OTP inputs are the classic case of fighting the browser instead of borrowing from it. One input, painted boxes, two magic attributes — the whole component. It lives in Velocity X's Forms category alongside the wizard, tag input and validation patterns, installed as source you own.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.