UI Snippet
🔐 OTP Verification Input
A 6-digit OTP input with auto-focus, paste support, and backspace navigation. Pure HTML, CSS, and JavaScript.
🔍 Live Demo
Enter any 6 digits. Try typing, backspace, or pasting a code.
Enter the 6-digit code sent to your email
Enter a code to continue.
📘 Try these features
✅ Auto-advances to next input when you type
✅ Backspace goes to previous input
✅ Paste a 6-digit code → auto-fills all boxes
✅ Only accepts numbers
✅ Try pasting:
✅ Backspace goes to previous input
✅ Paste a 6-digit code → auto-fills all boxes
✅ Only accepts numbers
✅ Try pasting:
123456
💻 Complete Code
<!-- HTML -->
<div class="otp-container">
<input type="text" maxlength="1" inputmode="numeric" class="otp-input" autocomplete="one-time-code">
<input type="text" maxlength="1" inputmode="numeric" class="otp-input">
<input type="text" maxlength="1" inputmode="numeric" class="otp-input">
<input type="text" maxlength="1" inputmode="numeric" class="otp-input">
<input type="text" maxlength="1" inputmode="numeric" class="otp-input">
<input type="text" maxlength="1" inputmode="numeric" class="otp-input">
</div>
<button id="verifyBtn" disabled>Verify Code</button>
<button id="resetBtn">Reset</button>
<!-- CSS -->
.otp-container {
display: flex;
gap: 0.5rem;
justify-content: center;
}
.otp-input {
width: 50px;
height: 60px;
background: #1e293b;
border: 2px solid #334155;
border-radius: 0.5rem;
color: white;
font-size: 1.5rem;
font-weight: 700;
text-align: center;
transition: all 0.2s;
}
.otp-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
.otp-input.filled {
border-color: #4ade80;
}
.otp-input.error {
border-color: #ef4444;
animation: shake 0.3s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
<!-- JavaScript -->
const inputs = document.querySelectorAll('.otp-input');
const verifyBtn = document.getElementById('verifyBtn');
const resetBtn = document.getElementById('resetBtn');
const output = document.getElementById('otpOutput');
// Focus first input on load
inputs[0].focus();
// Handle input
inputs.forEach((input, index) => {
// Typing
input.addEventListener('input', (e) => {
const value = e.target.value.replace(/\D/g, ''); // only digits
e.target.value = value;
if (value) {
e.target.classList.add('filled');
if (index < inputs.length - 1) {
inputs[index + 1].focus();
}
} else {
e.target.classList.remove('filled');
}
checkComplete();
});
// Backspace
input.addEventListener('keydown', (e) => {
if (e.key === 'Backspace' && !e.target.value && index > 0) {
inputs[index - 1].focus();
inputs[index - 1].value = '';
inputs[index - 1].classList.remove('filled');
checkComplete();
}
});
// Arrow keys
input.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' && index > 0) {
inputs[index - 1].focus();
}
if (e.key === 'ArrowRight' && index < inputs.length - 1) {
inputs[index + 1].focus();
}
});
// Paste
input.addEventListener('paste', (e) => {
e.preventDefault();
const pasted = (e.clipboardData || window.clipboardData)
.getData('text')
.replace(/\D/g, '')
.slice(0, 6);
if (!pasted) return;
pasted.split('').forEach((char, i) => {
if (inputs[i]) {
inputs[i].value = char;
inputs[i].classList.add('filled');
}
});
const nextEmpty = Math.min(pasted.length, inputs.length - 1);
inputs[nextEmpty].focus();
checkComplete();
});
});
// Enable verify button when all filled
function checkComplete() {
const complete = [...inputs].every(i => i.value.length === 1);
verifyBtn.disabled = !complete;
}
// Verify
verifyBtn.addEventListener('click', () => {
const code = [...inputs].map(i => i.value).join('');
output.innerHTML = `Verifying ${code}...`;
setTimeout(() => {
// Demo: 123456 is the "correct" code
if (code === '123456') {
output.innerHTML = `✅ Verified successfully! (Demo)`;
inputs.forEach(i => i.classList.add('filled'));
} else {
output.innerHTML = `❌ Invalid code. Try 123456 (demo).`;
inputs.forEach(i => {
i.classList.add('error');
setTimeout(() => i.classList.remove('error'), 400);
});
}
}, 800);
});
// Reset
resetBtn.addEventListener('click', () => {
inputs.forEach(i => {
i.value = '';
i.classList.remove('filled', 'error');
});
inputs[0].focus();
verifyBtn.disabled = true;
output.textContent = 'Enter a code to continue.';
});
🧠 How It Works
- Auto-focus: When you type a digit, focus jumps to the next box.
- Backspace: On an empty box, focus returns to the previous one and clears it.
- Paste: Pasting a 6-digit code fills all boxes at once.
- Input filtering:
replace(/\D/g, '')strips non-digits. - Arrow keys: Left/right arrows navigate between boxes.
- Verify button: Automatically enabled only when all 6 boxes are filled.
- Mobile friendly:
inputmode="numeric"shows the number pad on mobile. - Autofill support:
autocomplete="one-time-code"works with iOS/Android SMS autofill.
💡 Why This Snippet?
- No dependencies — pure HTML/CSS/JS, no libraries.
- Accessible — keyboard navigation, screen-reader friendly.
- Mobile-optimized — number keyboard, OTP autofill.
- Real-world UX — used by every major app (Google, Facebook, Amazon).
- Customizable — easy to change digit count, colors, or sizes.