No.157
MICRO CSSJS

OTP Input

6桁の認証コードを1桁ずつの枠に入力し、打つと次の枠へ、消すと前の枠へ移る入力欄。まとめて貼り付けると6枠に振り分けられ、SMSのコードの自動入力にも対応します。

DEMO

メールに届いた6桁のコードを入力してください

CODE

HTML
<div class="demo-stage otp-stage">
  <div class="otp-box">
    <p class="otp-lead" id="otp-lead">メールに届いた6桁のコードを入力してください</p>
    <div class="otp-fields" id="otp-fields" role="group" aria-labelledby="otp-lead"></div>
    <p class="otp-status" id="otp-status" role="status" aria-live="polite"></p>
  </div>
</div>
CSS
.otp-stage { display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:280px; }
.otp-box { text-align:center; }
.otp-lead { margin:0 0 1rem; color:#C9C9D1; font-size:.82rem; }
.otp-fields { display:flex; gap:.5rem; justify-content:center; }
.otp-cell { width:44px; height:54px; box-sizing:border-box; text-align:center; font-family:'JetBrains Mono',monospace; font-size:1.4rem; color:#fff;
  background:#15151B; border:1px solid rgba(255,255,255,.16); border-radius:10px; outline:none; caret-color:#E91B89;
  transition: border-color .15s ease, box-shadow .15s ease; }
.otp-cell:focus { border-color:#E91B89; box-shadow:0 0 0 3px rgba(233,27,137,.25); }
.otp-cell.is-filled { animation: otp-pop .22s cubic-bezier(.34,1.56,.64,1); }
.otp-fields.is-ok .otp-cell { border-color:#34D399; }
@keyframes otp-pop { 50% { transform:scale(1.08); } }
.otp-status { margin:.9rem 0 0; min-height:1.2em; font-size:.8rem; color:#34D399; }
@media (prefers-reduced-motion: reduce) {
  .otp-cell { transition-duration:.01ms; }
  .otp-cell.is-filled { animation:none; }
}
JS
(function(){
  var wrap = document.getElementById('otp-fields');
  var status = document.getElementById('otp-status');
  if (!wrap) return;
  var N = 6, cells = [];
  for (var i = 0; i < N; i++) {
    var c = document.createElement('input');
    c.className = 'otp-cell'; c.type = 'text'; c.inputMode = 'numeric';
    c.setAttribute('aria-label', (i + 1) + '桁目');
    c.autocomplete = i === 0 ? 'one-time-code' : 'off';
    wrap.appendChild(c); cells.push(c);
  }
  function pop(c){ c.classList.remove('is-filled'); void c.offsetWidth; c.classList.add('is-filled'); }
  function check(){
    var code = cells.map(function(c){ return c.value; }).join('');
    var ok = code.length === N;
    wrap.classList.toggle('is-ok', ok);
    status.textContent = ok ? 'コード ' + code + ' を確認しました' : '';
  }
  function fill(from, digits){
    for (var k = 0; k < digits.length && from + k < N; k++) { cells[from + k].value = digits[k]; pop(cells[from + k]); }
    cells[Math.min(from + digits.length, N - 1)].focus();
    check();
  }
  cells.forEach(function(c, i){
    c.addEventListener('input', function(){
      var d = c.value.replace(/\D/g, '');
      if (d.length > 1) { c.value = ''; fill(i, d); return; }
      c.value = d;
      if (d) { pop(c); if (i < N - 1) cells[i + 1].focus(); }
      check();
    });
    c.addEventListener('keydown', function(e){
      if (e.key === 'Backspace' && !c.value && i > 0) { e.preventDefault(); cells[i - 1].value = ''; cells[i - 1].focus(); check(); }
      else if (e.key === 'ArrowLeft' && i > 0) { e.preventDefault(); cells[i - 1].focus(); }
      else if (e.key === 'ArrowRight' && i < N - 1) { e.preventDefault(); cells[i + 1].focus(); }
    });
    c.addEventListener('paste', function(e){
      var t = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '');
      if (!t) return;
      e.preventDefault();
      fill(i, t);
    });
    c.addEventListener('focus', function(){ c.select(); });
  });
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
6桁の認証コード(ワンタイムパスワード)を1桁ずつの枠に入力する OTP Input を作って。input を6つ JS で作り、inputmode="numeric"・それぞれ aria-label(「1桁目」など)を付け、全体は role="group" で説明文と aria-labelledby で関連付ける。1つ目だけ autocomplete="one-time-code" にすると、スマホで SMS のコードの自動入力候補が出る。maxlength=1 は付けない(ブラウザによっては自動入力の6桁が1桁に切り詰められるため、桁の制御は input イベント側で行う)。入力されたら数字以外を取り除き、1桁入ったら次の枠へフォーカスを移す。空の枠で Backspace を押したら前の枠を消してそこへ戻る。←→キーで枠を移動する。6桁をまとめて貼り付けた時(paste イベント)や、自動入力で1つの枠に複数桁が入った時は、その位置から後ろの枠へ1桁ずつ振り分ける。入った枠は cubic-bezier(.34,1.56,.64,1) で一瞬だけ scale(1.08) に弾ませる。6桁そろったら枠を緑にし、role="status" aria-live="polite" の段落で確認したことを伝える。フォーカス時は中身を選択状態にして上書きしやすくする。@media (prefers-reduced-motion: reduce) では弾む動きを無効化する。

Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 マイクロインタラクション ―UI/UXデザインの神が宿る細部 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

RELATED EFFECTS

↗ MORE
→ FLOATING LABEL → PASSWORD STRENGTH METER → COPY BUTTON

OTP Input(認証コード入力欄)とは

メールやSMSで届く6桁程度の認証コードを、1桁ずつ区切られた枠に入力する部品です。二段階認証やログイン時の本人確認で広く使われ、桁数が目で見て分かるため、入力漏れや打ち間違いに気づきやすくなります。

当サイトの Floating Label は入力欄のラベルの動き、Password Strength Meter は入力内容の強さの表示です。本ページは1つの値を複数の枠に分けて入力させる別テーマで、フォーカスの自動移動と貼り付けの振り分けが品質を左右します。

見た目よりも入力の作法が重要です。自動で次へ進む、消したら戻る、貼り付けやSMSの自動入力でまとめて入る、の3つがそろわないと、1つの入力欄より使いにくい部品になってしまいます。

よくある質問

autocomplete="one-time-code" は何のためですか?

SMSで届いた認証コードを、キーボードの上に入力候補として表示させるための指定です。iPhone の Safari などで対応しており、利用者はタップするだけでコードを入力できます。自動入力は1つ目の枠にまとめて入るため、本実装では複数桁が入った時に後ろの枠へ振り分ける処理を入れています。

type="number" を使わないのはなぜですか?

type="number" は、↑↓キーやホイールで値が増減したり、e(指数表記)や小数点が入力できたり、数値として扱うと先頭の0が消えたりと、コードの入力には向かない動きがあります。type="text" に inputmode="numeric" を組み合わせると、スマホでは数字キーボードが開き、値は文字列のまま扱えます。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。