Skip to content

OTP Code Input

A copy-paste login & auth component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.

Login & AuthHTMLJavaScriptCSSany framework

Copy into your project

HTML
<div class="nuda-otp">
  <input class="nuda-otp__cell" maxlength="1" inputmode="numeric" />
  <input class="nuda-otp__cell" maxlength="1" inputmode="numeric" />
  <input class="nuda-otp__cell" maxlength="1" inputmode="numeric" />
  <input class="nuda-otp__cell" maxlength="1" inputmode="numeric" />
  <input class="nuda-otp__cell" maxlength="1" inputmode="numeric" />
  <input class="nuda-otp__cell" maxlength="1" inputmode="numeric" />
</div>
JavaScript
// Auto-advance + paste support for the OTP cells
const cells = document.querySelectorAll('.nuda-otp__cell');

cells.forEach((cell, i) => {
  cell.addEventListener('input', (e) => {
    if (e.target.value.length === 1) {
      cell.classList.add('is-filled');
      cells[i + 1]?.focus();
    } else {
      cell.classList.remove('is-filled');
    }
  });
  cell.addEventListener('keydown', (e) => {
    if (e.key === 'Backspace' && !cell.value) cells[i - 1]?.focus();
  });
});

cells[0].addEventListener('paste', (e) => {
  const text = (e.clipboardData?.getData('text') ?? '').slice(0, cells.length);
  e.preventDefault();
  [...text].forEach((char, i) => {
    if (cells[i]) {
      cells[i].value = char;
      cells[i].classList.add('is-filled');
    }
  });
  cells[Math.min(text.length, cells.length - 1)]?.focus();
});
CSS
.nuda-otp { display: flex; gap: 8px; }

.nuda-otp__cell {
  width: 40px;
  height: 50px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(0, 0, 0, 0.3);
  border-radius: 10px;
  color: #fafafa;
  text-align: center;
  font-size: 18px;
  font-weight: 600;
  outline: none;
  transition: border-color 0.2s, box-shadow 0.25s, transform 0.2s, background 0.25s;
}

.nuda-otp__cell:focus {
  border-color: #e4ff54;
  box-shadow: 0 0 0 3px rgba(228, 255, 84, 0.15);
  transform: translateY(-2px);
}

.nuda-otp__cell.is-filled {
  border-color: #e4ff54;
  background: rgba(228, 255, 84, 0.06);
  color: #e4ff54;
  animation: nuda-otp-pop 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes nuda-otp-pop {
  0%   { transform: scale(0.85); }
  60%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}

How to use OTP Code Input

Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.

Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.

More login & auth components

← Browse all NudaUI components