本文へスキップ

ステップフォーム

3ステップ(基本情報→住所→確認)を進捗バー付きで切り替え。次へ/戻るボタンと必須チェックを備え、最後に入力内容を確認表示する。

ENGLISH

Multi-Step Form (Wizard)

  • JS
約245行基本のUI部品 / フォーム入力 の一覧 →

プレビュー

入力

操作実際に入力して試せます。

仕様書

向く場面
  • 会員登録・予約など意味のまとまりで分かれる長いフォーム
  • 進捗を見せて離脱を減らしたい手続き
  • 最後に入力内容を確認させたい申込
避ける場面
  • 2〜3項目の短いフォーム(1画面の方が速い)
  • 任意のステップへ直接飛ばせたい画面(一方向遷移のみ)
構成要素
  • 進捗ドットと接続線
  • ステップ見出し
  • 入力パネル
  • 確認リスト
  • エラー文
  • 戻る・次へボタン
持っている状態
  • 通常
  • ホバー
  • フォーカス
  • エラー
  • 無効
  • 成功
キーボード

Tab で入力欄と戻る/次へを移動、Enter/Space で進む。必須未入力なら止まり最初の空欄へフォーカス。ステップ切替後は新しいパネルの最初の入力欄(確認画面は見出し)へフォーカスが移る

支援技術

ステップ見出し「ステップ N / 3」は aria-live=polite、ドットは aria-hidden(見出しが正)。必須は required+aria-required、赤い * は aria-hidden。エラー文は aria-live=assertive。パネルは hidden で切替

動き

ドット・線の色 250ms(進捗の更新)、ボタン背景 150ms。reduced-motion で無効

デザインの要点

  • パネルは全部 DOM に置いて hidden で切り替える。DOM から消すと戻ったときに入力値が失われる
  • 確認画面は各 input の値を都度読む。専用の状態を持たないので、戻って直しても必ず最新が出る
  • ステップを切り替えたら最初の入力欄へフォーカスを移す。移さないと、キーボードでは画面が変わったことに気づけない
  • 最後のボタンは文言を「送信」に変え色も変える。「次へ」のまま送信すると、まだ続きがあると思って押される

使いどころ

入力項目が意味のあるまとまりに分かれ、進捗を見せることに価値がある長めのフォーム(会員登録、予約手続きなど)に向く。この実装は基本情報→住所→確認の3ステップで、ドットと接続線・見出しテキストの3つで「今どこにいるか」を示す。項目数が少ない(2〜3項目程度の)フォームにはオーバースペックで、1画面にまとめた方が速い。またこの実装は前へ/次への一方向遷移のみで、進捗ドットをクリックして任意のステップへ直接ジャンプすることはできない。リロードやタブを閉じると入力内容は失われる(localStorageなど永続化する仕組みが無く、値は各inputのDOM状態そのものが唯一の保持場所である)ため、長時間かかる可能性のある申込フォームでは離脱リスクを別途考慮する必要がある。

仕組み

3つのパネルはすべてhidden属性で切り替えられ、DOMからは削除されない。goTo(s)が現在のパネルをhiddenにし、新しいステップのパネルを表示し、ドットの.active/.doneクラスと接続線の.doneクラス、見出しテキストを一括で更新する。ステップ2(確認画面)に来たときだけ showConfirm() が呼ばれ、これは各inputのvalueを都度DOMから直接読み取ってdl要素に書き出す。フォーム値を保持する専用のJSオブジェクトは存在せず、DOM上のinput自体が唯一の状態源になっているため、戻るボタンで前のステップに戻って値を書き換えても、確認画面に再度進めば必ず最新の値が反映される。検証はfirstEmpty(s)がそのステップに属するinputのvalue.trim()が空の要素を探すだけで、形式までは見ていない。

実装の注意

firstEmpty()は「空でないか」しか見ないため、メールアドレス欄に「abc」とだけ入力しても次のステップへ進めてしまう。形式チェックまで含めるなら、リアルタイム検証の実装のようにフィールドごとのルールを別途持たせる必要がある。次に、goTo()はパネルの表示切り替えとテキスト更新のあとに、新しいパネルの最初の入力欄(確認画面では見出し)へ focus() を移している。この1行を省くと、マウス操作なら見た目の切り替わりに気づけるが、キーボードユーザーやスクリーンリーダー利用者は次のステップへ進んだ後もフォーカスが押したボタンに残ったままになり、ステップが変わったことに気づきにくい。フォーカス先の見出しには tabindex=-1 を付けておく(付けないと focus() が効かない)。また [0,1,2]の配列、fieldsマップ、stepTitles配列など、ステップ数と要素IDがコード内の複数箇所にハードコードされているため、ステップを4つに増やす場合はこれらすべてを漏れなく更新する必要があり、1箇所でも直し忘れるとその分だけ挙動がずれる。

アクセシビリティ

必須項目はラベル内の赤いアスタリスク(span.ms-req、aria-hidden)で視覚的に示すと同時に、各 input に required と aria-required="true" を付けている。スクリーンリーダー利用者は視覚的な「*」を確認できないが、input に到達した時点で「必須」と読み上げられる。フォームには novalidate を付けていないが、送信ボタンではなく type="button" の「次へ」で進むため、検証は自前の firstEmpty() が担い、失敗時は #ms-err を表示して最初の空欄へフォーカスを移す。#ms-err は hidden 属性の切り替えで表示され、aria-live="assertive" なので、検証に失敗して足止めされたことがその場で読み上げられる(ブロッキングなエラーなので polite ではなく assertive)。進捗表示は、ドットを aria-hidden="true" にして装飾扱いとし、ステップ見出しテキスト(#ms-step-label「ステップ 2 / 3:住所」)を aria-live="polite" にして、切り替えのたびに今何ステップ目かを読み上げさせている。同じ情報を2経路で読ませない、という整理である。

調整して使う

現状は一方向の逐次遷移だが、完了済みステップのドットをクリックして直接戻れるようにする(未来のステップへは先送りできないよう、validate済みのステップだけ許可する)拡張は使い勝手が上がる。郵便番号から住所を自動補完するような非同期検証をステップの検証に組み込むなら、次へボタンを検証完了までdisabledにする状態を追加する必要がある。ステップ数や項目数が増えて確認画面のDOM直読みが煩雑になる場合は、各フィールドの値を専用の状態オブジェクトに集約してから確認画面を描画する構成に切り替えた方が保守しやすい。長時間離脱の可能性がある申込では、ステップ移動のたびにlocalStorageへ下書き保存する変更が有効である。

コード(コピーして使えます)

<div class="ms-wrap">
  <div class="ms-progress" aria-hidden="true">
    <div class="ms-step-dot active" id="ms-dot-0">1</div>
    <div class="ms-line" id="ms-line-0"></div>
    <div class="ms-step-dot" id="ms-dot-1">2</div>
    <div class="ms-line" id="ms-line-1"></div>
    <div class="ms-step-dot" id="ms-dot-2">3</div>
  </div>
  <div class="ms-step-label" id="ms-step-label" aria-live="polite">ステップ 1 / 3:基本情報</div>

  <div class="ms-panel" id="ms-panel-0">
    <div class="ms-field">
      <label class="ms-label" for="ms-name">お名前 <span class="ms-req" aria-hidden="true">*</span></label>
      <input id="ms-name" class="ms-input" type="text" placeholder="山田 太郎" autocomplete="name" required aria-required="true" />
    </div>
    <div class="ms-field">
      <label class="ms-label" for="ms-email">メール <span class="ms-req" aria-hidden="true">*</span></label>
      <input id="ms-email" class="ms-input" type="email" placeholder="taro@example.com" autocomplete="email" required aria-required="true" />
    </div>
  </div>

  <div class="ms-panel" id="ms-panel-1" hidden>
    <div class="ms-field">
      <label class="ms-label" for="ms-zip">郵便番号 <span class="ms-req" aria-hidden="true">*</span></label>
      <input id="ms-zip" class="ms-input" type="text" placeholder="123-4567" inputmode="numeric" autocomplete="postal-code" required aria-required="true" />
    </div>
    <div class="ms-field">
      <label class="ms-label" for="ms-addr">住所 <span class="ms-req" aria-hidden="true">*</span></label>
      <input id="ms-addr" class="ms-input" type="text" placeholder="東京都渋谷区…" autocomplete="street-address" required aria-required="true" />
    </div>
  </div>

  <div class="ms-panel" id="ms-panel-2" hidden>
    <p class="ms-confirm-title" id="ms-confirm-title" tabindex="-1">入力内容の確認</p>
    <dl class="ms-confirm" id="ms-confirm"></dl>
  </div>

  <p class="ms-err" id="ms-err" aria-live="assertive" hidden>必須項目を入力してください</p>

  <div class="ms-actions">
    <button class="ms-btn ms-back" id="ms-back" type="button" hidden>戻る</button>
    <button class="ms-btn ms-next" id="ms-next" type="button">次へ</button>
  </div>
</div>

タグ

同じカテゴリ「フォーム入力」のほかのパーツ

関連パーツ