Input OTP
A one-time passcode input using a single native
<input>, wrapped in
<span class="otp"> so it can be cropped to
look like separate boxes.
One real <input> underneath, not six inputs
stitched together with JavaScript. Paste, backspace, and mobile SMS
autofill through autocomplete="one-time-code" all work on
their own, and a screen reader announces one text field.
The wrapper only clips overflow. The input is rendered one cell wider than the wrapper, so the text cursor always has room and never scrolls the field. The tradeoff is that CSS can't count typed characters, so the focus ring covers the whole input instead of the box being filled.
Default
Set inputmode="numeric",
pattern="\d*", and maxlength to match
the number of digits.
<span class="otp">
<input
type="text"
inputmode="numeric"
pattern="\d*"
maxlength="4"
autocomplete="one-time-code"
aria-label="One-time passcode"
/>
</span>
Length
The number of boxes follows maxlength automatically.
Override --otp-length on the wrapper directly if you
ever need to.
<span class="otp">
<input
type="text"
inputmode="numeric"
pattern="\d*"
maxlength="4"
autocomplete="one-time-code"
aria-label="One-time passcode"
/>
</span>
Placeholder
A placeholder of dashes or dots reads naturally as
empty boxes, since the same letter spacing applies to it.
<span class="otp">
<input
type="text"
inputmode="numeric"
pattern="\d*"
maxlength="6"
autocomplete="one-time-code"
placeholder="000000"
aria-label="One-time passcode"
/>
</span>
In a field
Drop it inside a Field for a visible label and hint text.
<label class="field">
<span>Enter PIN</span>
<span class="otp">
<input
type="text"
inputmode="numeric"
pattern="\d*"
maxlength="4"
autocomplete="one-time-code"
/>
</span>
<small>We sent a 4-digit code to your phone.</small>
</label>
Disabled
<span class="otp">
<input type="text" inputmode="numeric" pattern="\d*" maxlength="6" disabled />
</span>