ステッパー
カート→配送→決済→完了など手順の進捗を示すインジケータ。次へ/戻るボタンで進められる。
プレビュー
操作クリック/タップで動きます。
使いどころ
チェックアウト・申し込みフォーム・オンボーディングなど、決まった手順を順番に進める複数ステップの処理に向く。今何ステップ目で、あと何ステップ残っているかを常に見せられるため、長いフォームを1画面ずつに分割しても迷いにくくなる。逆に、手順の順番が固定でない(ユーザーが好きな項目から埋められる設定画面など)や、ステップ数が2つしかない単純な確認画面には、ステッパーを置く分だけ画面が重くなり過剰装飾になりやすい。このデモは4ステップ固定のUIパーツで、実際のフォームでは各ステップのnext/backボタンに実際のバリデーションや画面遷移のロジックを結び付ける必要があり、現状はcurrentの数値を進退させてrender()を呼ぶだけの見本にとどまっている。
仕組み
状態は変数currentが単一のソースで、render()を呼ぶたびにstepsとlinesの全要素を舐めてis-done/is-activeクラスを再計算する単純な設計。i < currentは完了(is-done)、i === currentは現在地(is-active)、それ以外は未達という3分岐で、丸番号(.stp-num)と完了チェック(.stp-check)はどちらもdisplay:none/blockのトグルで排他に切り替えている。線(.stp-line)もis-doneクラスの有無だけで色を変えており、円と線を合わせて『どこまで進んだか』を一本の視覚的な帯として見せている。next/backボタンはcurrentをインクリメント/デクリメントしてrender()を呼び直すだけで、ステップごとの入力内容の保持やバリデーションは一切持たない(このデモの範囲外)。backBtnはdisabled属性で先頭を止めているが、nextBtnは末尾でstyle.opacityとstyle.cursorを直接書き換えるだけで、disabled属性そのものは付与していない点に注意。
実装の注意
nextBtnが末尾(isLast)のとき見た目だけopacity:0.4・cursor:defaultにしているが、disabled属性は付けていないため、ボタン自体はクリック可能かつフォーカス可能なままになっている。クリックハンドラ側でif (current < steps.length - 1)のガードがあるので実害(誤って進んでしまう)は起きないが、マウスカーソルの見た目だけを頼りに『押せない』と判断させる設計は誤解を招きやすい。本番で使うならnextBtn.disabled = isLastのように実際のdisabled属性も同期させるべき。もう一つ、ステップ数が可変(手順によって3つだったり5つだったりする)UIに転用する場合、labels配列とsteps/linesのNodeListの要素数を必ず一致させる必要があり、ズレるとmsg.textContent = labels[current]がundefinedを表示する。stp-descのmin-height:36pxは短い1行のメッセージを想定した固定値なので、2行に折り返す長い文言に差し替えると高さが揃わずガクつく。
アクセシビリティ
現在ステップの伝達がこの実装の最大の弱点で、is-activeクラスによる見た目の変化(枠の色・box-shadow)以外に、現在ステップであることをスクリーンリーダーへ伝える仕組みが一切ない。aria-currentがどの.stp-stepにも付いておらず、render()内で該当ステップにaria-current="step"を付け外しする処理を足す必要がある。また.stp-msgはただの<p>要素で、next/backのクリックのたびにtextContentが変わってもaria-live領域として宣言されていないため、スクリーンリーダー利用者には手順が進んだことも次の指示文もその場では読み上げられない(role="status" aria-live="polite"を付けるべき)。加えて、前述のとおりnextBtnは末尾で見た目上disabledに見えるがaria-disabledもdisabled属性も持たないため、スクリーンリーダー利用者には『押せるボタン』としてそのまま扱われてしまう。実装時はdisabled属性(またはaria-disabled="true")を確実に同期させること。
調整して使う
ステップ数を増減させる、円の中に数字ではなくアイコンを入れる、線を直線ではなく進捗バー的なグラデーションで塗るなどが主な調整点。横並びが窮屈なモバイル幅では、円と線の並びを縦に変えたり、ラベルを非表示にして『2/4』のようなテキストだけの簡易表示に切り替える構成もよく使われる。ステップ間をクリックで直接ジャンプできるようにする(すでに完了したステップへ戻れる)拡張も考えられるが、その場合はis-doneの円にもクリックイベントを足し、まだ到達していない未来のステップへは飛べないようガードするロジックが要る。バリデーションと連携する実装では、次へボタンを押した時点で入力エラーがあればcurrentを進めずにエラーメッセージをstp-desc相当の領域へ出す分岐を追加することになる。
コード(コピーして使えます)
<div class="stp-root">
<div class="stp-track">
<div class="stp-step is-done" data-index="0">
<div class="stp-circle"><span class="stp-check">✓</span><span class="stp-num">1</span></div>
<span class="stp-label">カート</span>
</div>
<div class="stp-line"></div>
<div class="stp-step is-active" data-index="1">
<div class="stp-circle"><span class="stp-check">✓</span><span class="stp-num">2</span></div>
<span class="stp-label">配送</span>
</div>
<div class="stp-line"></div>
<div class="stp-step" data-index="2">
<div class="stp-circle"><span class="stp-check">✓</span><span class="stp-num">3</span></div>
<span class="stp-label">決済</span>
</div>
<div class="stp-line"></div>
<div class="stp-step" data-index="3">
<div class="stp-circle"><span class="stp-check">✓</span><span class="stp-num">4</span></div>
<span class="stp-label">完了</span>
</div>
</div>
<div class="stp-desc">
<p class="stp-msg">配送先を入力してください</p>
</div>
<div class="stp-actions">
<button class="stp-back" disabled>← 戻る</button>
<button class="stp-next">次へ →</button>
</div>
</div>