マルチステップ・ウィザード

アカウント→プロフィール→確認の3ステップに分けた入力フォーム。上部の進捗インジケータと次へ/戻るボタンでスライド遷移し、最後に入力内容をまとめて確認できます。

#form#wizard#steps#ui

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<div class="wiz-card">
  <div class="fd-brand"><span class="fd-mark">◇</span> FlowDesk セットアップ</div>

  <!-- 上部のステップ進捗インジケータ -->
  <ol class="wiz-steps" id="wiz-steps">
    <li class="wiz-step is-active" data-step="0">
      <span class="wiz-dot">1</span>
      <span class="wiz-label">アカウント</span>
    </li>
    <li class="wiz-step" data-step="1">
      <span class="wiz-dot">2</span>
      <span class="wiz-label">ワークスペース</span>
    </li>
    <li class="wiz-step" data-step="2">
      <span class="wiz-dot">3</span>
      <span class="wiz-label">確認</span>
    </li>
  </ol>

  <!-- スライドするパネル群 -->
  <div class="wiz-viewport">
    <form class="wiz-track" id="wiz-track" novalidate>
      <!-- ステップ1: アカウント -->
      <section class="wiz-panel" aria-label="アカウント">
        <label class="wiz-field">
          <span>仕事用メールアドレス</span>
          <input type="email" name="email" placeholder="you@company.com" autocomplete="off">
        </label>
        <label class="wiz-field">
          <span>パスワード</span>
          <input type="password" name="password" placeholder="8文字以上" autocomplete="off">
        </label>
      </section>

      <!-- ステップ2: ワークスペース -->
      <section class="wiz-panel" aria-label="ワークスペース">
        <label class="wiz-field">
          <span>ワークスペース名</span>
          <input type="text" name="name" placeholder="例: 営業チーム" autocomplete="off">
        </label>
        <label class="wiz-field">
          <span>契約プラン</span>
          <select name="plan">
            <option value="スターター">スターター</option>
            <option value="ビジネス">ビジネス</option>
            <option value="エンタープライズ">エンタープライズ</option>
          </select>
        </label>
      </section>

      <!-- ステップ3: 確認サマリ -->
      <section class="wiz-panel" aria-label="確認">
        <p class="wiz-summary-head">入力内容の確認</p>
        <dl class="wiz-summary" id="wiz-summary"></dl>
      </section>
    </form>
  </div>

  <!-- 操作ボタン -->
  <div class="wiz-nav">
    <button type="button" class="wiz-btn ghost" id="wiz-prev" disabled>戻る</button>
    <button type="button" class="wiz-btn solid" id="wiz-next">次へ</button>
  </div>

  <p class="wiz-status" id="wiz-status" role="status"></p>
</div>
CSS
:root {
  /* FlowDeskテーマ用CSS変数 */
  --accent: #4f7cff;
  --accent2: #2f5fe0;
  --line: rgba(120, 150, 220, 0.28);
  --ink: #e8edf7;
  --muted: #8ea0c4;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background:
    radial-gradient(80% 60% at 50% 0%, rgba(79, 124, 255, 0.32), transparent 60%),
    #0f1b34;
  color: var(--ink);
}

.wiz-card {
  width: min(420px, 94vw);
  padding: 20px 22px 16px;
  background: #16244a;
  border: 1px solid rgba(120, 150, 220, 0.22);
  border-radius: 16px;
  box-shadow: 0 26px 60px -24px rgba(0, 0, 0, 0.8);
}
.fd-brand {
  display: flex; align-items: center; gap: 7px; margin-bottom: 16px;
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.05em; color: #9fb6f0;
}
.fd-mark { color: #4f7cff; font-size: 1rem; }

/* 進捗インジケータ */
.wiz-steps {
  list-style: none; display: flex; justify-content: space-between;
  margin: 0 0 20px; padding: 0; position: relative;
}
.wiz-steps::before {
  content: ""; position: absolute; top: 14px; left: 14px; right: 14px;
  height: 2px; background: var(--line);
}
.wiz-step { position: relative; z-index: 1; display: grid; justify-items: center; gap: 6px; flex: 1; }
.wiz-dot {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  font-size: 0.82rem; font-weight: 700; color: var(--muted);
  background: #16244a; border: 2px solid var(--line); border-radius: 50%;
  transition: all 0.25s ease;
}
.wiz-label { font-size: 0.72rem; color: var(--muted); font-weight: 600; transition: color 0.25s ease; }

.wiz-step.is-active .wiz-dot {
  color: #fff; border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 8px 18px -8px rgba(79, 124, 255, 0.85);
}
.wiz-step.is-active .wiz-label { color: var(--ink); }
.wiz-step.is-done .wiz-dot { color: #fff; border-color: transparent; background: #34d399; font-size: 0; }
.wiz-step.is-done .wiz-dot::after { content: "✓"; font-size: 0.9rem; }

/* スライド領域 */
.wiz-viewport { overflow: hidden; }
.wiz-track {
  display: flex; width: 300%; transform: translateX(0);
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.wiz-panel {
  width: 33.3333%; flex: 0 0 33.3333%;
  padding: 4px 2px; display: grid; gap: 12px;
  align-content: start; min-height: 152px;
}

.wiz-field { display: grid; gap: 5px; }
.wiz-field span { font-size: 0.76rem; font-weight: 600; color: var(--muted); }
.wiz-field input,
.wiz-field select {
  width: 100%; padding: 10px 12px;
  font-size: 0.9rem; color: var(--ink);
  background: #0f1b34;
  border: 2px solid var(--line); border-radius: 10px; outline: none;
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.wiz-field input::placeholder { color: #5f719a; }
.wiz-field input:focus,
.wiz-field select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(79, 124, 255, 0.2);
}
.wiz-field input.invalid { border-color: #f87171; }

/* 確認サマリ */
.wiz-summary-head { margin: 2px 0 4px; font-size: 0.9rem; font-weight: 700; color: var(--ink); }
.wiz-summary { margin: 0; display: grid; gap: 8px; }
.wiz-row { display: flex; justify-content: space-between; gap: 12px; padding: 9px 12px; background: #0f1b34; border-radius: 10px; }
.wiz-row dt { margin: 0; font-size: 0.76rem; color: var(--muted); font-weight: 600; }
.wiz-row dd { margin: 0; font-size: 0.84rem; color: var(--ink); font-weight: 600; text-align: right; word-break: break-all; }

/* ナビゲーション */
.wiz-nav { display: flex; gap: 10px; margin-top: 16px; }
.wiz-btn {
  flex: 1; padding: 11px 14px;
  font-size: 0.88rem; font-weight: 700;
  border: none; border-radius: 11px; cursor: pointer;
  transition: transform 0.1s ease, opacity 0.16s ease;
}
.wiz-btn.solid {
  color: #fff; background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 12px 22px -12px rgba(79, 124, 255, 0.85);
}
.wiz-btn.ghost { color: var(--muted); background: rgba(120, 150, 220, 0.14); }
.wiz-btn:not(:disabled):hover { transform: translateY(-1px); }
.wiz-btn:disabled { opacity: 0.45; cursor: not-allowed; }

.wiz-status { margin: 10px 0 0; min-height: 16px; text-align: center; font-size: 0.78rem; font-weight: 600; color: var(--accent); }
.wiz-status.ok { color: #34d399; }
.wiz-status.err { color: #f87171; }

@media (prefers-reduced-motion: reduce) {
  .wiz-track, .wiz-dot, .wiz-btn, .wiz-field input, .wiz-field select { transition: none; }
}
JavaScript
// 要素取得(null安全)
const track = document.getElementById("wiz-track");
const stepsEl = document.getElementById("wiz-steps");
const prevBtn = document.getElementById("wiz-prev");
const nextBtn = document.getElementById("wiz-next");
const statusEl = document.getElementById("wiz-status");
const summaryEl = document.getElementById("wiz-summary");

if (track && stepsEl && prevBtn && nextBtn) {
  const steps = [...stepsEl.querySelectorAll(".wiz-step")];
  const TOTAL = steps.length; // 全3ステップ
  let current = 0;

  // 各ステップの必須チェック定義
  const validators = [
    () => {
      const email = track.elements.email;
      const pw = track.elements.password;
      const okEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim());
      const okPw = pw.value.length >= 8;
      mark(email, okEmail);
      mark(pw, okPw);
      return okEmail && okPw ? "" : "メールと8文字以上のパスワードを入力してください";
    },
    () => {
      const name = track.elements.name;
      const okName = name.value.trim().length > 0;
      mark(name, okName);
      return okName ? "" : "ワークスペース名を入力してください";
    },
    () => "" // 確認ステップは検証不要
  ];

  // 入力欄の妥当性を見た目に反映
  function mark(input, ok) {
    if (input) input.classList.toggle("invalid", !ok);
  }

  // 表示中ステップへスライド&インジケータ更新
  function render() {
    track.style.transform = `translateX(-${current * 33.3333}%)`;
    steps.forEach((s, i) => {
      s.classList.toggle("is-active", i === current);
      s.classList.toggle("is-done", i < current);
    });
    prevBtn.disabled = current === 0;
    nextBtn.textContent = current === TOTAL - 1 ? "作成する" : "次へ";
    setStatus("");
  }

  // ステータスメッセージ表示
  function setStatus(msg, type = "") {
    statusEl.textContent = msg;
    statusEl.className = "wiz-status" + (type ? " " + type : "");
  }

  // 最終ステップ手前で確認サマリを生成
  function buildSummary() {
    const pwLen = track.elements.password.value.length;
    const rows = [
      ["メールアドレス", track.elements.email.value.trim()],
      ["パスワード", "•".repeat(pwLen)],
      ["ワークスペース名", track.elements.name.value.trim()],
      ["契約プラン", track.elements.plan.value]
    ];
    summaryEl.innerHTML = rows
      .map(([k, v]) => `<div class="wiz-row"><dt>${k}</dt><dd>${escapeHtml(v)}</dd></div>`)
      .join("");
  }

  // XSS対策の簡易エスケープ
  function escapeHtml(str) {
    return String(str).replace(/[&<>"']/g, (c) =>
      ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c])
    );
  }

  // 「次へ / 作成する」
  nextBtn.addEventListener("click", () => {
    const error = validators[current]();
    if (error) { setStatus(error, "err"); return; }

    if (current < TOTAL - 1) {
      current += 1;
      if (current === TOTAL - 1) buildSummary(); // 確認画面へ入る直前に集計
      render();
    } else {
      // 実送信はしない(デモ)
      setStatus("ワークスペースを作成しました ✓", "ok");
      nextBtn.disabled = true;
      prevBtn.disabled = true;
      steps.forEach((s) => s.classList.add("is-done"));
    }
  });

  // 「戻る」
  prevBtn.addEventListener("click", () => {
    if (current > 0) { current -= 1; render(); }
  });

  // フォーム送信はすべて抑止
  track.addEventListener("submit", (e) => e.preventDefault());

  render();
}

コード

HTML
<div class="stage">
  <div class="wiz-card">
    <!-- 上部のステップ進捗インジケータ -->
    <ol class="wiz-steps" id="wiz-steps">
      <li class="wiz-step is-active" data-step="0">
        <span class="wiz-dot">1</span>
        <span class="wiz-label">アカウント</span>
      </li>
      <li class="wiz-step" data-step="1">
        <span class="wiz-dot">2</span>
        <span class="wiz-label">プロフィール</span>
      </li>
      <li class="wiz-step" data-step="2">
        <span class="wiz-dot">3</span>
        <span class="wiz-label">確認</span>
      </li>
    </ol>

    <!-- スライドするパネル群 -->
    <div class="wiz-viewport">
      <form class="wiz-track" id="wiz-track" novalidate>
        <!-- ステップ1: アカウント -->
        <section class="wiz-panel" aria-label="アカウント">
          <label class="wiz-field">
            <span>メールアドレス</span>
            <input type="email" name="email" placeholder="you@example.com" autocomplete="off">
          </label>
          <label class="wiz-field">
            <span>パスワード</span>
            <input type="password" name="password" placeholder="8文字以上" autocomplete="off">
          </label>
        </section>

        <!-- ステップ2: プロフィール -->
        <section class="wiz-panel" aria-label="プロフィール">
          <label class="wiz-field">
            <span>表示名</span>
            <input type="text" name="name" placeholder="山田 太郎" autocomplete="off">
          </label>
          <label class="wiz-field">
            <span>プラン</span>
            <select name="plan">
              <option value="フリー">フリー</option>
              <option value="プロ">プロ</option>
              <option value="チーム">チーム</option>
            </select>
          </label>
        </section>

        <!-- ステップ3: 確認サマリ -->
        <section class="wiz-panel" aria-label="確認">
          <p class="wiz-summary-head">入力内容の確認</p>
          <dl class="wiz-summary" id="wiz-summary"></dl>
        </section>
      </form>
    </div>

    <!-- 操作ボタン -->
    <div class="wiz-nav">
      <button type="button" class="wiz-btn ghost" id="wiz-prev" disabled>戻る</button>
      <button type="button" class="wiz-btn solid" id="wiz-next">次へ</button>
    </div>

    <p class="wiz-status" id="wiz-status" role="status"></p>
  </div>
</div>
CSS
:root {
  /* テーマ用CSS変数 */
  --accent: #6366f1;
  --accent2: #8b5cf6;
  --line: #e2e8f0;
  --ink: #1e293b;
  --muted: #64748b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%);
  color: var(--ink);
}

.stage { width: 100%; padding: 20px; display: grid; place-items: center; }

.wiz-card {
  width: min(420px, 94vw);
  padding: 22px 22px 18px;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 24px 60px -26px rgba(30, 41, 59, 0.45);
}

/* 進捗インジケータ */
.wiz-steps {
  list-style: none;
  display: flex;
  justify-content: space-between;
  margin: 0 0 20px;
  padding: 0;
  position: relative;
}
.wiz-steps::before {
  /* 全ステップを貫く下地ライン */
  content: "";
  position: absolute;
  top: 14px; left: 14px; right: 14px;
  height: 2px;
  background: var(--line);
}
.wiz-step {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 6px;
  flex: 1;
}
.wiz-dot {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--muted);
  background: #fff;
  border: 2px solid var(--line);
  border-radius: 50%;
  transition: all 0.25s ease;
}
.wiz-label { font-size: 0.72rem; color: var(--muted); font-weight: 600; transition: color 0.25s ease; }

.wiz-step.is-active .wiz-dot {
  color: #fff;
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 8px 18px -8px rgba(99, 102, 241, 0.8);
}
.wiz-step.is-active .wiz-label { color: var(--ink); }
.wiz-step.is-done .wiz-dot {
  color: #fff;
  border-color: transparent;
  background: #34d399;
}
.wiz-step.is-done .wiz-dot::after { content: "✓"; } /* 完了は記号で表示 */
.wiz-step.is-done .wiz-dot { font-size: 0; }
.wiz-step.is-done .wiz-dot::after { font-size: 0.9rem; }

/* スライド領域 */
.wiz-viewport { overflow: hidden; }
.wiz-track {
  display: flex;
  width: 300%;
  transform: translateX(0);
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.wiz-panel {
  width: 33.3333%;
  flex: 0 0 33.3333%;
  padding: 4px 2px;
  display: grid;
  gap: 12px;
  align-content: start;
  min-height: 152px;
}

.wiz-field { display: grid; gap: 5px; }
.wiz-field span { font-size: 0.76rem; font-weight: 600; color: var(--muted); }
.wiz-field input,
.wiz-field select {
  width: 100%;
  padding: 10px 12px;
  font-size: 0.9rem;
  color: var(--ink);
  background: #f8fafc;
  border: 2px solid var(--line);
  border-radius: 10px;
  outline: none;
  transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease;
}
.wiz-field input:focus,
.wiz-field select:focus {
  border-color: var(--accent);
  background: #fff;
  box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.14);
}
.wiz-field input.invalid { border-color: #f87171; background: #fef2f2; }

/* 確認サマリ */
.wiz-summary-head { margin: 2px 0 4px; font-size: 0.9rem; font-weight: 700; color: var(--ink); }
.wiz-summary { margin: 0; display: grid; gap: 8px; }
.wiz-row { display: flex; justify-content: space-between; gap: 12px; padding: 9px 12px; background: #f1f5f9; border-radius: 10px; }
.wiz-row dt { margin: 0; font-size: 0.76rem; color: var(--muted); font-weight: 600; }
.wiz-row dd { margin: 0; font-size: 0.84rem; color: var(--ink); font-weight: 600; text-align: right; word-break: break-all; }

/* ナビゲーション */
.wiz-nav { display: flex; gap: 10px; margin-top: 16px; }
.wiz-btn {
  flex: 1;
  padding: 11px 14px;
  font-size: 0.88rem;
  font-weight: 700;
  border: none;
  border-radius: 11px;
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.16s ease, opacity 0.16s ease;
}
.wiz-btn.solid {
  color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 12px 22px -12px rgba(99, 102, 241, 0.85);
}
.wiz-btn.ghost { color: var(--muted); background: #f1f5f9; }
.wiz-btn:not(:disabled):hover { transform: translateY(-1px); }
.wiz-btn:not(:disabled):active { transform: translateY(0); }
.wiz-btn:disabled { opacity: 0.45; cursor: not-allowed; }

.wiz-status { margin: 10px 0 0; min-height: 16px; text-align: center; font-size: 0.78rem; font-weight: 600; color: var(--accent); }
.wiz-status.ok { color: #059669; }
.wiz-status.err { color: #dc2626; }

@media (prefers-reduced-motion: reduce) {
  .wiz-track, .wiz-dot, .wiz-btn, .wiz-field input, .wiz-field select { transition: none; }
}
JavaScript
// 要素取得(null安全)
const track = document.getElementById("wiz-track");
const stepsEl = document.getElementById("wiz-steps");
const prevBtn = document.getElementById("wiz-prev");
const nextBtn = document.getElementById("wiz-next");
const statusEl = document.getElementById("wiz-status");
const summaryEl = document.getElementById("wiz-summary");

if (track && stepsEl && prevBtn && nextBtn) {
  const steps = [...stepsEl.querySelectorAll(".wiz-step")];
  const TOTAL = steps.length; // 全3ステップ
  let current = 0;

  // 各ステップの必須チェック定義
  const validators = [
    () => {
      const email = track.elements.email;
      const pw = track.elements.password;
      const okEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim());
      const okPw = pw.value.length >= 8;
      mark(email, okEmail);
      mark(pw, okPw);
      return okEmail && okPw ? "" : "メールと8文字以上のパスワードを入力してください";
    },
    () => {
      const name = track.elements.name;
      const okName = name.value.trim().length > 0;
      mark(name, okName);
      return okName ? "" : "表示名を入力してください";
    },
    () => "" // 確認ステップは検証不要
  ];

  // 入力欄の妥当性を見た目に反映
  function mark(input, ok) {
    if (input) input.classList.toggle("invalid", !ok);
  }

  // 表示中ステップへスライド&インジケータ更新
  function render() {
    track.style.transform = `translateX(-${current * 33.3333}%)`;
    steps.forEach((s, i) => {
      s.classList.toggle("is-active", i === current);
      s.classList.toggle("is-done", i < current);
    });
    prevBtn.disabled = current === 0;
    nextBtn.textContent = current === TOTAL - 1 ? "登録する" : "次へ";
    setStatus("");
  }

  // ステータスメッセージ表示
  function setStatus(msg, type = "") {
    statusEl.textContent = msg;
    statusEl.className = "wiz-status" + (type ? " " + type : "");
  }

  // 最終ステップ手前で確認サマリを生成
  function buildSummary() {
    const pwLen = track.elements.password.value.length;
    const rows = [
      ["メールアドレス", track.elements.email.value.trim()],
      ["パスワード", "•".repeat(pwLen)],
      ["表示名", track.elements.name.value.trim()],
      ["プラン", track.elements.plan.value]
    ];
    summaryEl.innerHTML = rows
      .map(([k, v]) => `<div class="wiz-row"><dt>${k}</dt><dd>${escapeHtml(v)}</dd></div>`)
      .join("");
  }

  // XSS対策の簡易エスケープ
  function escapeHtml(str) {
    return String(str).replace(/[&<>"']/g, (c) =>
      ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c])
    );
  }

  // 「次へ / 登録する」
  nextBtn.addEventListener("click", () => {
    const error = validators[current]();
    if (error) { setStatus(error, "err"); return; }

    if (current < TOTAL - 1) {
      current += 1;
      if (current === TOTAL - 1) buildSummary(); // 確認画面へ入る直前に集計
      render();
    } else {
      // 実送信はしない(デモ)
      setStatus("登録が完了しました ✓", "ok");
      nextBtn.disabled = true;
      prevBtn.disabled = true;
      steps.forEach((s) => s.classList.add("is-done"));
    }
  });

  // 「戻る」
  prevBtn.addEventListener("click", () => {
    if (current > 0) { current -= 1; render(); }
  });

  // フォーム送信はすべて抑止
  track.addEventListener("submit", (e) => e.preventDefault());

  render();
}

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「マルチステップ・ウィザード」の効果を追加してください。

# 追加してほしい効果
マルチステップ・ウィザード(フォーム & 入力)
アカウント→プロフィール→確認の3ステップに分けた入力フォーム。上部の進捗インジケータと次へ/戻るボタンでスライド遷移し、最後に入力内容をまとめて確認できます。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<div class="stage">
  <div class="wiz-card">
    <!-- 上部のステップ進捗インジケータ -->
    <ol class="wiz-steps" id="wiz-steps">
      <li class="wiz-step is-active" data-step="0">
        <span class="wiz-dot">1</span>
        <span class="wiz-label">アカウント</span>
      </li>
      <li class="wiz-step" data-step="1">
        <span class="wiz-dot">2</span>
        <span class="wiz-label">プロフィール</span>
      </li>
      <li class="wiz-step" data-step="2">
        <span class="wiz-dot">3</span>
        <span class="wiz-label">確認</span>
      </li>
    </ol>

    <!-- スライドするパネル群 -->
    <div class="wiz-viewport">
      <form class="wiz-track" id="wiz-track" novalidate>
        <!-- ステップ1: アカウント -->
        <section class="wiz-panel" aria-label="アカウント">
          <label class="wiz-field">
            <span>メールアドレス</span>
            <input type="email" name="email" placeholder="you@example.com" autocomplete="off">
          </label>
          <label class="wiz-field">
            <span>パスワード</span>
            <input type="password" name="password" placeholder="8文字以上" autocomplete="off">
          </label>
        </section>

        <!-- ステップ2: プロフィール -->
        <section class="wiz-panel" aria-label="プロフィール">
          <label class="wiz-field">
            <span>表示名</span>
            <input type="text" name="name" placeholder="山田 太郎" autocomplete="off">
          </label>
          <label class="wiz-field">
            <span>プラン</span>
            <select name="plan">
              <option value="フリー">フリー</option>
              <option value="プロ">プロ</option>
              <option value="チーム">チーム</option>
            </select>
          </label>
        </section>

        <!-- ステップ3: 確認サマリ -->
        <section class="wiz-panel" aria-label="確認">
          <p class="wiz-summary-head">入力内容の確認</p>
          <dl class="wiz-summary" id="wiz-summary"></dl>
        </section>
      </form>
    </div>

    <!-- 操作ボタン -->
    <div class="wiz-nav">
      <button type="button" class="wiz-btn ghost" id="wiz-prev" disabled>戻る</button>
      <button type="button" class="wiz-btn solid" id="wiz-next">次へ</button>
    </div>

    <p class="wiz-status" id="wiz-status" role="status"></p>
  </div>
</div>

【CSS】
:root {
  /* テーマ用CSS変数 */
  --accent: #6366f1;
  --accent2: #8b5cf6;
  --line: #e2e8f0;
  --ink: #1e293b;
  --muted: #64748b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%);
  color: var(--ink);
}

.stage { width: 100%; padding: 20px; display: grid; place-items: center; }

.wiz-card {
  width: min(420px, 94vw);
  padding: 22px 22px 18px;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 24px 60px -26px rgba(30, 41, 59, 0.45);
}

/* 進捗インジケータ */
.wiz-steps {
  list-style: none;
  display: flex;
  justify-content: space-between;
  margin: 0 0 20px;
  padding: 0;
  position: relative;
}
.wiz-steps::before {
  /* 全ステップを貫く下地ライン */
  content: "";
  position: absolute;
  top: 14px; left: 14px; right: 14px;
  height: 2px;
  background: var(--line);
}
.wiz-step {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 6px;
  flex: 1;
}
.wiz-dot {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--muted);
  background: #fff;
  border: 2px solid var(--line);
  border-radius: 50%;
  transition: all 0.25s ease;
}
.wiz-label { font-size: 0.72rem; color: var(--muted); font-weight: 600; transition: color 0.25s ease; }

.wiz-step.is-active .wiz-dot {
  color: #fff;
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 8px 18px -8px rgba(99, 102, 241, 0.8);
}
.wiz-step.is-active .wiz-label { color: var(--ink); }
.wiz-step.is-done .wiz-dot {
  color: #fff;
  border-color: transparent;
  background: #34d399;
}
.wiz-step.is-done .wiz-dot::after { content: "✓"; } /* 完了は記号で表示 */
.wiz-step.is-done .wiz-dot { font-size: 0; }
.wiz-step.is-done .wiz-dot::after { font-size: 0.9rem; }

/* スライド領域 */
.wiz-viewport { overflow: hidden; }
.wiz-track {
  display: flex;
  width: 300%;
  transform: translateX(0);
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.wiz-panel {
  width: 33.3333%;
  flex: 0 0 33.3333%;
  padding: 4px 2px;
  display: grid;
  gap: 12px;
  align-content: start;
  min-height: 152px;
}

.wiz-field { display: grid; gap: 5px; }
.wiz-field span { font-size: 0.76rem; font-weight: 600; color: var(--muted); }
.wiz-field input,
.wiz-field select {
  width: 100%;
  padding: 10px 12px;
  font-size: 0.9rem;
  color: var(--ink);
  background: #f8fafc;
  border: 2px solid var(--line);
  border-radius: 10px;
  outline: none;
  transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease;
}
.wiz-field input:focus,
.wiz-field select:focus {
  border-color: var(--accent);
  background: #fff;
  box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.14);
}
.wiz-field input.invalid { border-color: #f87171; background: #fef2f2; }

/* 確認サマリ */
.wiz-summary-head { margin: 2px 0 4px; font-size: 0.9rem; font-weight: 700; color: var(--ink); }
.wiz-summary { margin: 0; display: grid; gap: 8px; }
.wiz-row { display: flex; justify-content: space-between; gap: 12px; padding: 9px 12px; background: #f1f5f9; border-radius: 10px; }
.wiz-row dt { margin: 0; font-size: 0.76rem; color: var(--muted); font-weight: 600; }
.wiz-row dd { margin: 0; font-size: 0.84rem; color: var(--ink); font-weight: 600; text-align: right; word-break: break-all; }

/* ナビゲーション */
.wiz-nav { display: flex; gap: 10px; margin-top: 16px; }
.wiz-btn {
  flex: 1;
  padding: 11px 14px;
  font-size: 0.88rem;
  font-weight: 700;
  border: none;
  border-radius: 11px;
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.16s ease, opacity 0.16s ease;
}
.wiz-btn.solid {
  color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 12px 22px -12px rgba(99, 102, 241, 0.85);
}
.wiz-btn.ghost { color: var(--muted); background: #f1f5f9; }
.wiz-btn:not(:disabled):hover { transform: translateY(-1px); }
.wiz-btn:not(:disabled):active { transform: translateY(0); }
.wiz-btn:disabled { opacity: 0.45; cursor: not-allowed; }

.wiz-status { margin: 10px 0 0; min-height: 16px; text-align: center; font-size: 0.78rem; font-weight: 600; color: var(--accent); }
.wiz-status.ok { color: #059669; }
.wiz-status.err { color: #dc2626; }

@media (prefers-reduced-motion: reduce) {
  .wiz-track, .wiz-dot, .wiz-btn, .wiz-field input, .wiz-field select { transition: none; }
}

【JavaScript】
// 要素取得(null安全)
const track = document.getElementById("wiz-track");
const stepsEl = document.getElementById("wiz-steps");
const prevBtn = document.getElementById("wiz-prev");
const nextBtn = document.getElementById("wiz-next");
const statusEl = document.getElementById("wiz-status");
const summaryEl = document.getElementById("wiz-summary");

if (track && stepsEl && prevBtn && nextBtn) {
  const steps = [...stepsEl.querySelectorAll(".wiz-step")];
  const TOTAL = steps.length; // 全3ステップ
  let current = 0;

  // 各ステップの必須チェック定義
  const validators = [
    () => {
      const email = track.elements.email;
      const pw = track.elements.password;
      const okEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim());
      const okPw = pw.value.length >= 8;
      mark(email, okEmail);
      mark(pw, okPw);
      return okEmail && okPw ? "" : "メールと8文字以上のパスワードを入力してください";
    },
    () => {
      const name = track.elements.name;
      const okName = name.value.trim().length > 0;
      mark(name, okName);
      return okName ? "" : "表示名を入力してください";
    },
    () => "" // 確認ステップは検証不要
  ];

  // 入力欄の妥当性を見た目に反映
  function mark(input, ok) {
    if (input) input.classList.toggle("invalid", !ok);
  }

  // 表示中ステップへスライド&インジケータ更新
  function render() {
    track.style.transform = `translateX(-${current * 33.3333}%)`;
    steps.forEach((s, i) => {
      s.classList.toggle("is-active", i === current);
      s.classList.toggle("is-done", i < current);
    });
    prevBtn.disabled = current === 0;
    nextBtn.textContent = current === TOTAL - 1 ? "登録する" : "次へ";
    setStatus("");
  }

  // ステータスメッセージ表示
  function setStatus(msg, type = "") {
    statusEl.textContent = msg;
    statusEl.className = "wiz-status" + (type ? " " + type : "");
  }

  // 最終ステップ手前で確認サマリを生成
  function buildSummary() {
    const pwLen = track.elements.password.value.length;
    const rows = [
      ["メールアドレス", track.elements.email.value.trim()],
      ["パスワード", "•".repeat(pwLen)],
      ["表示名", track.elements.name.value.trim()],
      ["プラン", track.elements.plan.value]
    ];
    summaryEl.innerHTML = rows
      .map(([k, v]) => `<div class="wiz-row"><dt>${k}</dt><dd>${escapeHtml(v)}</dd></div>`)
      .join("");
  }

  // XSS対策の簡易エスケープ
  function escapeHtml(str) {
    return String(str).replace(/[&<>"']/g, (c) =>
      ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c])
    );
  }

  // 「次へ / 登録する」
  nextBtn.addEventListener("click", () => {
    const error = validators[current]();
    if (error) { setStatus(error, "err"); return; }

    if (current < TOTAL - 1) {
      current += 1;
      if (current === TOTAL - 1) buildSummary(); // 確認画面へ入る直前に集計
      render();
    } else {
      // 実送信はしない(デモ)
      setStatus("登録が完了しました ✓", "ok");
      nextBtn.disabled = true;
      prevBtn.disabled = true;
      steps.forEach((s) => s.classList.add("is-done"));
    }
  });

  // 「戻る」
  prevBtn.addEventListener("click", () => {
    if (current > 0) { current -= 1; render(); }
  });

  // フォーム送信はすべて抑止
  track.addEventListener("submit", (e) => e.preventDefault());

  render();
}

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。