Input OTP
N adjacent <input class="otp-slot"> in .otp. Digits only. A digit moves focus forward;
Backspace on an empty slot moves back. role="group" names the cluster via aria-labelledby; each slot has its own aria-label. These are real inputs, not one fake field.
There is no wrapper.
Preview
One-time code
Code
<script>
function otp_event(event) {
const input = event.target;
if (!input.classList.contains('otp-slot')) return;
const slots = [...input.closest('.otp').querySelectorAll('.otp-slot')];
const i = slots.indexOf(input);
if (event.type === 'beforeinput') {
if (event.inputType.startsWith('insert') && !(event.data && /^\d+$/.test(event.data)))
event.preventDefault();
return;
}
if (event.type === 'input') {
const digit = input.value.replace(/\D/g, '').slice(-1);
input.value = digit;
if (digit) slots[i + 1]?.focus();
return;
}
if (event.type === 'keydown' && event.key === 'Backspace' && !input.value && i > 0) {
event.preventDefault();
slots[i - 1].focus();
return;
}
if (event.type === 'focus')
input.select();
}
</script>
<div class="field">
<div class="label" id="otp-label">One-time code</div>
<div class="otp" role="group" aria-labelledby="otp-label">
<input class="otp-slot" type="text" inputmode="numeric" maxlength="1" spellcheck="false" autocapitalize="off" pattern="[0-9]" onbeforeinput={otp_event} oninput={otp_event} onkeydown={otp_event} onfocus={otp_event} autocomplete="one-time-code" aria-label="Digit 1 of 6">
<input class="otp-slot" type="text" inputmode="numeric" maxlength="1" spellcheck="false" autocapitalize="off" pattern="[0-9]" onbeforeinput={otp_event} oninput={otp_event} onkeydown={otp_event} onfocus={otp_event} aria-label="Digit 2 of 6">
<input class="otp-slot" type="text" inputmode="numeric" maxlength="1" spellcheck="false" autocapitalize="off" pattern="[0-9]" onbeforeinput={otp_event} oninput={otp_event} onkeydown={otp_event} onfocus={otp_event} aria-label="Digit 3 of 6">
<input class="otp-slot" type="text" inputmode="numeric" maxlength="1" spellcheck="false" autocapitalize="off" pattern="[0-9]" onbeforeinput={otp_event} oninput={otp_event} onkeydown={otp_event} onfocus={otp_event} aria-label="Digit 4 of 6">
<input class="otp-slot" type="text" inputmode="numeric" maxlength="1" spellcheck="false" autocapitalize="off" pattern="[0-9]" onbeforeinput={otp_event} oninput={otp_event} onkeydown={otp_event} onfocus={otp_event} aria-label="Digit 5 of 6">
<input class="otp-slot" type="text" inputmode="numeric" maxlength="1" spellcheck="false" autocapitalize="off" pattern="[0-9]" onbeforeinput={otp_event} oninput={otp_event} onkeydown={otp_event} onfocus={otp_event} aria-label="Digit 6 of 6">
</div>
</div>