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.
It's a real, single <input> under the hood, not
several inputs stitched together with JavaScript. That means
paste, backspace, and mobile SMS autofill via
autocomplete="one-time-code" all work natively, and
screen readers announce it as one text field. The wrapper's only
job is to clip the input's own overflow: the input is rendered a
cell wider than the wrapper, so there's always slack room for the
text cursor and it never has to auto-scroll into view. The
tradeoff: CSS can't read how many characters have been typed, so
the focus ring highlights the whole input rather than just the
box currently 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>