OTP 입력

OTP input

문자 메시지 등으로 받은 짧은 인증번호를 빠르게 채워 넣도록 만든 고정 길이 입력.

다른 이름: One-time code inputPIN inputVerification code input
···
html
<div class="otw">
  <span class="otlabel">인증번호 6자리</span>
  <div class="otgroup" role="group" aria-label="인증번호 입력">
    <input class="otcell" maxlength="1" inputmode="numeric" aria-label="1번째 자리">
    <input class="otcell" maxlength="1" inputmode="numeric" aria-label="2번째 자리">
    <input class="otcell" maxlength="1" inputmode="numeric" aria-label="3번째 자리">
    <input class="otcell" maxlength="1" inputmode="numeric" aria-label="4번째 자리">
    <input class="otcell" maxlength="1" inputmode="numeric" aria-label="5번째 자리">
    <input class="otcell" maxlength="1" inputmode="numeric" aria-label="6번째 자리">
  </div>
  <p class="otstatus" id="otstatus">문자로 받은 6자리를 입력하세요</p>
</div>
css
.otw{position:absolute;inset:0;display:grid;place-items:center;gap:10px}
.otlabel{color:var(--muted);font-size:10px}
.otgroup{display:flex;gap:6px}
.otcell{width:26px;height:32px;text-align:center;font-size:15px;font-weight:700;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--fg)}
.otcell[data-filled]{border-color:var(--accent);color:var(--accent)}
.otstatus{font-size:10px;color:var(--muted)}
js
const cells = [...document.querySelectorAll('.otcell')];
const status = document.getElementById('otstatus');
cells.forEach((c, i) => {
  c.addEventListener('input', () => {
    auto = false;
    c.toggleAttribute('data-filled', !!c.value);
    if (c.value && cells[i + 1]) cells[i + 1].focus();
  });
  c.addEventListener('keydown', (e) => {
    if (e.key === 'Backspace' && !c.value && cells[i - 1]) { auto = false; cells[i - 1].focus(); }
  });
});
let auto = true;
function loop() {
  if (!auto) return;
  let i = 0;
  const t = setInterval(() => {
    if (!auto) return clearInterval(t);
    cells[i].value = String(Math.floor(Math.random() * 10));
    cells[i].setAttribute('data-filled', '');
    i++;
    if (i >= cells.length) {
      clearInterval(t);
      status.textContent = '확인 중…';
      setTimeout(() => {
        if (!auto) return;
        status.textContent = '인증 완료';
        setTimeout(() => {
          if (!auto) return;
          cells.forEach((c) => { c.value = ''; c.removeAttribute('data-filled'); });
          status.textContent = '문자로 받은 6자리를 입력하세요';
          setTimeout(loop, 900);
        }, 1200);
      }, 500);
    }
  }, 220);
}
setTimeout(loop, 500);

숫자 한 자리씩 들어가는 칸 여러 개(대개 6칸)로 보이지만, 접근성·자동완성 관점에서 권장되는 실제 구현은 <input> 하나에 autocomplete="one-time-code"와 inputmode="numeric"을 주는 것입니다 — 이러면 iOS/Android가 문자 메시지의 인증번호를 키보드 위 배너로 자동 채워주고, WebOTP API를 쓰면 사용자 동의 한 번으로 JS가 코드를 직접 받아올 수도 있습니다. 칸을 여러 개로 나눠 보여주고 싶다면 letter-spacing으로 시각적으로만 나누고 input은 하나로 유지하는 방법과, 정말 칸마다 별도 input을 두고 한 글자 입력 시 다음 칸으로 자동 포커스를 옮기는 방법 둘 다 흔합니다 — 후자를 쓴다면 각 칸에 "1번째 자리"처럼 위치를 알리는 라벨이 필요합니다.

숫자 입력(스피너)과는 다릅니다 — 숫자 입력은 하나의 수량 값을 늘리고 줄이는 데 최적화돼 있고, OTP 입력은 짧은 시간 안에 붙여넣기나 자동완성으로 빠르게 채워야 하는 고정 길이 문자열에 최적화돼 있습니다.

붙여넣기(6자리 전체를 한 번에 붙임)를 지원하려면 paste 이벤트에서 문자열을 잘라 칸마다 나눠 채우는 처리가 따로 필요합니다.