Recipe
The right input for one-time codes
A six-digit SMS code the user should never have to type by hand.
<form action="#">
<input type="text" inputmode="numeric" autocomplete="one-time-code" />
</form>iOS: simulated · not yet device-verified · scope iOS 26, Safari, en-US
Android · Gboard: simulated · not yet device-verified · scope Android 16, Chrome, en-US
Recipe updated 2026-07-25 · dataset 2026-07-24
One field, not six boxes. When the SMS arrives, both platforms offer the code above the keyboard — one tap fills it. On iOS 26.x that autofill pastes the entire code into the focused field, so six-box layouts end up with all six digits crammed into box one and five empty boxes. A single input receives the whole code correctly, works better with screen readers, and can still look like the six-box design with wide letter-spacing.
inputmode="numeric" summons the digit pad without dragging in
type="number"’s side effects — leading zeros survive (a code like 042519
arrives intact), and there are no spinner arrows for a desktop scroll wheel to
nudge.
autocomplete="one-time-code" is the part that raises the code suggestion when
the SMS lands. It needs a secure context (HTTPS), and it behaves most reliably
with the input inside a <form>.
Validate the code in JavaScript and on the server regardless — paste, dictation, and hardware keyboards bypass the digit pad entirely.
Gotchas
OTP: one field, not six boxes
In multi-box OTP layouts, iOS 26.x SMS autofill pastes the entire code into the first box instead of distributing the digits.
Do: Use ONE input with autocomplete="one-time-code" plus inputmode="numeric", not one box per digit.
Apple Developer Forums thread 807907 · WebKit bug 257804: one-time-code failures in iPhone Safari
OTP autofill degrades in iOS WebViews
Inside iOS WebViews (in-app browsers), one-time-code suggestions can stop appearing after the first successful use.
The keyboard is not validation
Keyboard choice only shapes convenience. Paste, dictation, hardware keyboards, and autofill all bypass the layout, so the value still needs JS and server-side validation.