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.









