GODRICH

入力欄 デザイン 9選 — コピペで動くフォーム部品

入力欄 デザインとは、電話番号やカード番号、数量、タグのように会員登録や購入フォームでよく使うのに、素のHTMLだけでは思うように動かない入力ウィジェットのことです。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9個は人気順ではなく、フォームを実際に埋めていく順番で並べています。連絡先と決済情報の入力(01〜03)、数量とタグの選択(04〜05)、メモの記入と添付(06〜07)、最後に本人確認と送信前の検証(08〜09)という流れです。

# 部品 いつ使うか
01 電話番号の自動ハイフン 会員登録・配送先連絡先
02 カード番号4桁区切り 決済フォームのカード欄
03 金額のカンマ自動入力 寄付・見積もり金額
04 数量 +/- ステッパー カート数量・予約人数
05 タグ入力欄 キーワード・宛先の複数登録
06 自動で伸びるtextarea レビュー・問い合わせ内容
07 ファイルのドラッグ&ドロップ プロフィール写真・添付
08 認証コード再送信タイマーボタン 電話・メールの再認証
09 リアルタイム入力検証表示 メール・パスワードの検証

01電話番号の自動ハイフン

数字だけ入力しても、010-1234-5678のように自動的にハイフンが入ります。まとめて貼り付けても同じルールで整形されます。

010 패턴자동 하이픈숫자만
function format(raw) {
  const d = raw.replace(/\D/g, "").slice(0, 11);
  if (d.length < 4) return d;
  if (d.length < 8) return d.slice(0, 3) + "-" + d.slice(3);
  return d.slice(0, 3) + "-" + d.slice(3, 7) + "-" + d.slice(7);
}
input.addEventListener("input", () => {
  input.value = format(input.value);
});

02カード番号4桁区切り

カード番号を入力すると4桁ごとに自動で空白が入り、実際のカードのように見えます。先頭の数字からVISA・Mastercard・AMEXも判定します。

4자리 그룹16자리 제한숫자만
function format(raw) {
  const d = raw.replace(/\D/g, "").slice(0, 16);
  const groups = d.match(/.{1,4}/g) || [];
  return groups.join(" ");
}
function brandOf(d) {
  if (d[0] === "4") return "VISA";
  if (/^5[1-5]/.test(d)) return "Mastercard";
  return "";
}

03金額のカンマ自動入力

円マークが付いた入力欄に数字を入れると、3桁ごとに自動でカンマが付きます。先頭の余分な0も同時に取り除かれます。

₩ 접두어천 단위 콤마우측 정렬
function format(raw) {
  const d = raw.replace(/\D/g, "").replace(/^0+(?=\d)/, "").slice(0, 12);
  return d ? Number(d).toLocaleString("ja-JP") : "";
}
input.addEventListener("input", () => {
  input.value = format(input.value);
});

04数量 +/- ステッパー

ボタンを押すたびに中央の数字が1ずつ変わり、最小値(1)になると−ボタンが自動的に薄くなって反応しなくなります。

최소/최대 제한키보드 지원숫자 직접 입력
const MIN = 1, MAX = 99;
function set(v) {
  n = Math.min(MAX, Math.max(MIN, v));
  value.value = n;
  minus.disabled = n <= MIN;
  plus.disabled = n >= MAX;
}
minus.addEventListener("click", () => set(n - 1));
plus.addEventListener("click", () => set(n + 1));

05タグ入力欄

文字を入力してEnterを押すとピル型のタグになり、空の入力欄でバックスペースを押すと最後のタグが消えます。

Enter로 추가×로 삭제백스페이스 삭제
input.addEventListener("keydown", (e) => {
  if (e.key === "Enter" || e.key === ",") {
    e.preventDefault();
    if (input.value.trim()) tags.push(input.value.trim());
    input.value = "";
    render();
  } else if (e.key === "Backspace" && !input.value && tags.length) {
    tags.pop();
    render();
  }
});

06自動で伸びるtextarea

行が増えるたびにスクロールの代わりに入力欄自体の高さが伸び、決めた最大の高さ(132px)を超えるとそこからスクロールになります。

scrollHeight 기반최대 높이 제한리사이즈 불가
function resize() {
  // 一度autoに戻さないと、縮んだ分の高さが反映されない
  area.style.height = "auto";
  const next = Math.min(area.scrollHeight, 132);
  area.style.height = next + "px";
}
area.addEventListener("input", resize);

07ファイルのドラッグ&ドロップ

ファイルをドラッグすると枠線が太くなり、ドロップした瞬間にファイル名がチップに変わります。クリックすれば通常のファイル選択も開きます。

dragover 표시클릭해서 찾아보기키보드 접근
zone.addEventListener("dragover", (e) => {
  e.preventDefault();
  zone.classList.add("is-over");
});
zone.addEventListener("drop", (e) => {
  e.preventDefault();
  zone.classList.remove("is-over");
  const file = e.dataTransfer.files[0];
  if (file) filename.textContent = "選択済み: " + file.name;
});

08認証コード再送信タイマーボタン

再送信ボタンを押すと60秒間無効になり、残り秒数がボタンの文字の上でリアルタイムに減っていき、0秒でまた押せるようになります。

60초 카운트다운중복 클릭 방지aria-live 안내
function cooldown(seconds) {
  let left = seconds;
  btn.disabled = true;
  btn.textContent = left + "秒後に再送信";
  const t = setInterval(() => {
    left--;
    if (left <= 0) {
      clearInterval(t);
      btn.disabled = false;
      btn.textContent = "再送信";
    } else {
      btn.textContent = left + "秒後に再送信";
    }
  }, 1000);
}

09リアルタイム入力検証表示

入力している間、メールの形式が合えば緑のチェックが、間違っていれば赤いメッセージがその場ですぐ表示されます。空欄のときは何も表示しません。

실시간 검증role="status" 알림성공/실패 아이콘
const RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
input.addEventListener("input", () => {
  const v = input.value;
  frame.classList.remove("is-ok", "is-bad");
  if (!v) { icon.textContent = ""; msg.textContent = ""; return; }
  if (RE.test(v)) { frame.classList.add("is-ok"); icon.textContent = "✓"; }
  else { frame.classList.add("is-bad"); icon.textContent = "!"; msg.textContent = "メール形式をご確認ください"; }
});

どこで壊れるか — 落とし穴ひとつ

01・02・03の自動整形入力欄は、すべて同じ落とし穴を抱えています。input.value = format(value)のように値をまるごと書き直すと、ブラウザはカーソル位置を必ず文字列の末尾に戻してしまいます。途中の桁を直そうと入力欄の真ん中をクリックしても、次の一文字を打った瞬間にカーソルが末尾へ飛んでしまうのです。電話番号やカード番号のような短い入力ではさほど気になりませんが、長く編集する入力欄ではMDNのHTMLInputElement.setSelectionRangeのドキュメントを参考に、整形前にカーソル位置を保存して整形後に戻す処理が必要です。この落とし穴を含めて9個のファイルすべてに実際に動作するバニラJSとReact版を収めた圧縮ファイルのパスワードはpuc8udvvで、下のボタンから受け取れます。

07のドラッグ&ドロップにも似た見落としがあります。dragoverイベントでpreventDefault()を呼ばないと、ブラウザの既定の動作(ファイルを新しいタブで開く)が実行されてしまい、dropイベント自体が発生しません。上のコードにこの処理が入っているのはそのためです。

アクセシビリティ

9個すべてでラベルと入力欄をlabelforで結び付けており、04の数量ステッパーは画面に見えないaria-live領域で「数量3個」のように変更値をスクリーンリーダーに知らせます。08の再送信ボタンはaria-live="polite"の状態文で送信完了を伝え、09の検証メッセージはrole="status"で値が変わったときだけ静かに更新されます。07のファイル入力は画面上では隠れていますがTabでフォーカスが移り、EnterSpaceでファイル選択が開くため、マウスなしでも最後まで操作できます。色や枠線の切り替えはすべてMDNのprefers-reduced-motionのドキュメントに沿って処理しています。

@media (prefers-reduced-motion: reduce) {
  .field__input, .drop__zone, .valid__frame { transition: none; }
}

ほかの入力フォーム関連の記事は認証コード 入力画面が直面する9つの状況で読めますし、このサイトが何を扱っているかは紹介ページにまとめています。

FAQ

この9個はReactのプロジェクトにもそのまま使えますか?

はい。圧縮ファイルのreact/フォルダには項目ごとに.tsx.module.scssが入っており、vanilla/のバニラJSと同じ動作をフックに移し替えてあります。色やラベルの文字列をpropsで渡すだけで使えます。

カード番号の自動整形は決済のセキュリティに関係しますか?

いいえ。ここで扱っているのは数字を4桁ごとに読みやすく画面表示するだけの整形処理で、カード情報を実際に処理・保存するのは決済代行会社の役目です。この整形自体はカードのデータをサーバーへ送信しません。

電話番号やカード番号の入力欄では、スマートフォンで数字キーパッドが自動で開きますか?

はい。inputmode="numeric"を指定しているため、多くのモバイルブラウザは文字キーボードの代わりに数字キーパッドを先に表示します。ただしこれはあくまでヒントなので、ブラウザによって多少動作が異なることがあります。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。