GODRICH

会員登録 フォーム デザイン 入力欄9選 — キーボード操作対応

会員登録 フォーム デザインの良し悪しは送信ボタンではなく入力欄で決まります。ここに並べた9つは、登録画面が実際に求める入力欄で、メールアドレス欄からクリアボタンまで、コードを貼ればそのまま動きます。

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

並び順は画面に出てくる順です。一番上のメール欄(01)はアイコンと単位で何を書く欄か先に教え、名前欄(02)はラベルが上に浮いて場所を節約し、生年月日(03)と電話番号(04)は数字を人が読む形に組み直します。次は住所(05)や興味分野(06)のように一覧から選んで埋める欄です。最後の3つは自己紹介を書いている間に起きることです。メンションとハッシュタグ(07)は書きながら色が付き、文字数カウンター(08)は上限を見張り、クリアボタン(09)は書き直したくなったら一度に空にします。一覧が開く3つ(04·05·06)には role="listbox"aria-activedescendant が付いて矢印キーが実際に効き、Escape は04·05·06·09の4つが受け付けます。

01アイコン・単位付き入力欄

左にメールアイコン、右に kg の単位が絶対配置で付いていて、触る前に何を書く欄か分かります。フォーカスが来ると 2px の枠とアイコン・単位が同じ色に変わります。アイコンと単位には aria-hidden="true"pointer-events: none が付いていてクリックを横取りしません。

아이콘 접두단위 접미포커스 테두리
  &__input {
    box-sizing: border-box;
    width: 100%;
    border: 2px solid transparent;
    border-radius: $r-control;
    padding: $sp-3 $sp-4;
    font-weight: 500;
    font-size: clamp(14px, 4.2vw, 16px);
    font-family: inherit;
    color: $stage-ink;
    background: #fff;
    transition: border-color $duration $easing;

    &--prefix { padding-left: $sp-10; }
    &--suffix { padding-right: $sp-8; }

    &:focus {
      outline: none;
      border-color: $color;
      & ~ .affix__unit { color: $color; }
    }
  }

02フローティングラベル入力欄

空欄ではラベルが枠の真ん中にありますが、フォーカスか文字1つで translateY(-14px) scale(.78) で上に浮いて小さくなります。JS は使わず :focus:not(:placeholder-shown) の2つのセレクタだけで作ってあり、placeholder=" " の1文字がこの仕掛けの材料です。

label transform포커스 색 전환채움 상태 유지
  &__label {
    position: absolute;
    left: $sp-4;
    top: 50%;
    transform: translateY(-50%) scale(1);
    transform-origin: left center;
    font-size: 16px;
    font-weight: 500;
    font-family: inherit;
    color: rgba(23, 20, 26, .48);
    pointer-events: none;
    transition: transform $duration $easing, color $duration $easing;
  }

  &__input:focus + &__label,
  &__input:not(:placeholder-shown) + &__label {
    transform: translateY(-14px) scale(.78);
    color: $color;
  }

03日付の自動スラッシュ入力欄

数字だけを打つと 2026/09/11 のように4桁目と6桁目の後にスラッシュが入ります。カレンダーを開くのではなく inputmode="numeric" を付けたテキスト欄なので、スマホでは最初に数字キーパッドが出ます。9桁目以降は切り捨てられます。

YYYY/MM/DD숫자만 허용자동 슬래시
  function format(raw) {
    var digits = raw.replace(/\D/g, '').slice(0, 8);
    var out = digits.slice(0, 4);
    if (digits.length > 4) out += '/' + digits.slice(4, 6);
    if (digits.length > 6) out += '/' + digits.slice(6, 8);
    return out;
  }

  input.addEventListener('input', function () {
    input.value = format(input.value);
  });

04国番号選択付き電話番号入力欄

国旗ボタンで国を選ぶと国番号が切り替わり、番号欄はその国の桁の区切りで書き直されます。韓国 +82 は 3·4·4、アメリカ +1 は 3·3·4、日本 +81 は 2·4·4 で切られるため、同じ10桁でも読み方が変わります。

국기 드롭다운국가별 자릿수키보드로 국가 탐색
  trigger.addEventListener('keydown', function (e) {
    if (!open) {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        openListbox();
      }
      return;
    }
    if (e.key === 'ArrowDown') { e.preventDefault(); highlight((active + 1) % options.length); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); highlight((active - 1 + options.length) % options.length); }
    else if (e.key === 'Enter') { e.preventDefault(); selectCountry(options[active].dataset.code); closeListbox(true); }
    else if (e.key === 'Escape') { e.preventDefault(); closeListbox(true); }
    else if (e.key === 'Tab') { closeListbox(false); }
  });

05住所オートコンプリート風入力欄

住所を打つと、下に出る候補は固定された5行のうち一致するものだけが残り、矢印キーと Enter で選んで欄を埋められます。入力欄には role="combobox"aria-autocomplete="list" が付いています。候補はあらかじめ決めた住所から選ぶ静的な一覧なので API キーは要りません。

role listbox화살표 키 탐색정적 후보 목록
  input.addEventListener('keydown', function (e) {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (!open && matches.length) { openList(); setActive(0); return; }
      if (open && matches.length) setActive(Math.min(active + 1, matches.length - 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (open) { if (active <= 0) setActive(-1); else setActive(active - 1); }
    } else if (e.key === 'Enter') {
      if (open && active > -1) { e.preventDefault(); selectOption(active, true); }
    } else if (e.key === 'Escape') {
      if (open) { e.preventDefault(); closeList(); setActive(-1); }
    }
  });

06複数選択コンボボックス

デザイン・開発・マーケティング・企画の4行から複数を選ぶと、選ぶたびにピル型のチップが入力欄の中に積み上がります。一覧には aria-multiselectable="true" が付いているので2つ目を選んでも1つ目が解除されません。チップは chip-pop のキーフレームで少し行き過ぎてから元の大きさに収まります。

체크박스 목록선택 칩 표시키보드로 토글
@keyframes chip-pop {
  0%   { transform: scale(.5); opacity: 0; }
  70%  { transform: scale(1.06); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .multi__listbox { transition: none !important; }
  .multi__trigger, .multi__chevron, .multi__check { transition: none; }
  .multi__chip { animation: none; }
}

07メンション・ハッシュタグ強調入力欄

@名前 や #タグ を打った瞬間、その単語だけに色が付いて残りの文はそのままです。contenteditable は使わず、文字を隠した textarea の下に同じ幅と同じ折り返し規則の backdrop を重ねて、カーソルがあるべき場所に留まるようにしています。

contenteditable실시간 하이라이트커서 위치 유지
  &__backdrop,
  &__input {
    box-sizing: border-box;
    width: 100%;
    min-height: 96px;
    max-height: 160px;
    padding: $sp-3 $sp-4;
    border: 2px solid transparent;
    border-radius: $r-control;
    font-weight: 500;
    font-size: clamp(13px, 3.8vw, 15px);
    line-height: 1.5;
    font-family: inherit;
    white-space: pre-wrap;
    word-wrap: break-word;
    overflow-wrap: break-word;
  }

08文字数カウンター入力欄

右下の数字が 0/80 から増えていき、残りが12文字以下になると数字と枠が赤くなります。上限そのものは HTML の maxlength="80" が守り、数字には aria-live="polite" が付いているため、画面を見ていない人にも今の文字数が読み上げられます。

maxlength 연동aria-live 안내임계값 색 전환
  function update() {
    var len = area.value.length;
    var remaining = MAX - len;
    count.textContent = len + '/' + MAX;
    if (remaining <= WARN) {
      count.classList.add('is-warn');
      area.classList.add('is-warn');
    } else {
      count.classList.remove('is-warn');
      area.classList.remove('is-warn');
    }
  }
  area.addEventListener('input', update);

09クリア(×)ボタン付き入力欄

1文字でも打つと右に丸い×ボタンが現れ、押すと欄が空になってフォーカスはその欄に残ります。ボタンは値がない間は opacity: 0pointer-events: none で完全に退避していて、中身ができた瞬間だけ戻ってきます。

조건부 표시aria-label 지우기focus 유지
  &__clear {
    position: absolute;
    top: 50%;
    right: $sp-2;
    transform: translateY(-50%) scale(.5);
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: rgba(23, 20, 26, .08);
    color: $stage-ink;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity $duration $easing, transform $duration $easing, background $duration $easing;

    &:hover, &:focus-visible { background: rgba(23, 20, 26, .16); }
  }

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

壊れ方は縦方向で、幅 320px で現れました。07 と 08 はどちらも3行の textarea を中に持っていて、入力欄の最小の高さを 96px のままにするとカード全体がその時点のステージより 07 は 2px、08 は 3px 高くなり、下の枠が切れていました。480px のデモサイズでは余白が問題を隠していたため、広い画面を目で確認しただけではこの欠陥は見つかりませんでした。直し方は入力欄を流れから外すことではなく、画面の幅が 340px 以下に下がった瞬間から min-height を 96px から 88px へ下げる @media (max-width: 340px) ブロックを足すことでした。ラベルとヒントの文字はそのままに、入力欄の縦だけ 8px 縮めると、両方の項目からはみ出し判定が消えました。この規則を入れたソースは、解凍パスワード ttw779pg で開く zip の中に9つ分そろえて入っています。

アクセシビリティ

9つとも prefers-reduced-motion: reduce では transition を止めても状態はそのまま残します。枠とアイコンの色は切り替わりの動きなしで即座に変わり、浮いていたラベルは浮いた位置に立ったまま、06 のチップは animation: none で跳ねずにそのまま現れます。一覧が開く3つ(04·05·06)は aria-expanded で開閉を伝え、aria-activedescendant でフォーカスを移さずに指している行だけを変え、08 の文字数は aria-live="polite" で読み上げられます。フォーカスを戻すときに必ず focus({ preventScroll: true }) を使うのは、このデモが記事の中に iframe で埋め込まれているためで、素の focus() だと読んでいる人の画面が下へ引っ張られるからです。ここで使った入力属性の意味は MDN の input 要素の解説にまとまっています。

同じ場所で使える部品はフォーム・入力カテゴリに、入力が間違っていたときに震える反応はエラーカテゴリにまとめてあります。

FAQ

カレンダーが開く生年月日欄があるのに、なぜ自分で作るのですか

ブラウザのカレンダーは信用できますが、見た目がブラウザごとに違いますし、速く打つ人にとっては、開いて閉じる時間の分だけ入力がむしろ遅くなります。この欄は数字だけを受け付けて 2026/09/11 の形に組み立てるので、カレンダーなしで書式が崩れません。カレンダーが欲しい画面では両方を並べます。

9つともマウスなしで使えますか

使えます。01·02·03·07·08 は普通の input と textarea なので、ブラウザが無料でやってくれることをそのまま使います。一覧が開く 04·05·06 は矢印で移動し、Enter で確定し、Escape で閉じます。04 と 06 は矢印が両端で折り返し、Tab で離れると一覧が閉まります。09 は入力欄で Escape を押すと値が消えます。

React 版で上限や色を変えられますか

08 の文字数の上限は max という props で変えられて、初期値は 80 です。警告が始まる残り12文字はモジュール内の定数なので、変えるにはその行を書き換えます。色と速さは、バニラ版では style.scss の一番上にある $duration·$easing·$color の3行を、React 版では各 module.scss に直接書かれた値を変えます。

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

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