ナビゲーション

進行ステッパー

手順進捗を段階的に示し、戻る/進むや完了到達を確認できるステップUIです。

動きを試す

プレビュー

プレビュー設定

プレビューを準備しています。

必要なファイルを選ぶ

コード

index.html

利用前に変更する箇所

  • 実際の手順名と説明に置き換える

<!doctype html>
<html lang="ja" data-theme="light">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>進行ステッパー</title>
    <style>
      :root { --bg:#f7f8fa; --surface:#ffffff; --text:#18212f; --muted:#5d6b7a; --border:#d7dee8; --ok:#1f9d55; --focus:#0077cc; --accent:#245fd6; }
      :root[data-theme="dark"] { --bg:#0f141a; --surface:#171d26; --text:#f2f5f8; --muted:#aab4c0; --border:#354152; --ok:#4ec97f; --focus:#65c7ff; --accent:#7ea7ff; }
      * { box-sizing: border-box; }
      body { margin: 0; padding: 16px; background: var(--bg); color: var(--text); font-family: Arial, sans-serif; }
      .card { max-width: 720px; margin: 0 auto; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
      .steps { display: grid; gap: 8px; }
      .step { border: 1px solid var(--border); border-radius: 10px; padding: 10px; background: var(--surface); }
      .step[data-progress="complete"] { border-color: var(--ok); }
      .step[data-progress="complete"]::after { content: "完了済み"; color: var(--ok); }
      .step[data-progress="current"] { border-color: var(--accent); }
      .step[data-progress="upcoming"]::after { content: "未完了"; color: var(--muted); }
      button { min-height: 44px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text); padding: 8px 12px; margin-right: 8px; }
      button:focus-visible { outline: 3px solid var(--focus); }
      .status { min-height: 1.4em; }
      @media (max-width: 360px) { body { padding: 10px; } }
      @media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
    </style>
  </head>
  <body>
    <main class="card">
      <h1>進行ステップ</h1>
      <ol class="steps">
        <li class="step" data-step="step1" data-progress="current" aria-current="step">
          <h2>開始</h2>
          <p>情報入力を行い、次へ進むための条件を満たします。</p>
          <p class="status">進捗: 1/3</p>
        </li>
        <li class="step" data-step="step2" data-progress="upcoming">
          <h2>確認</h2>
          <p>内容を確認し、変更可能な項目を提示します。</p>
          <p class="status">進捗: 2/3</p>
        </li>
        <li class="step" data-step="done" data-progress="upcoming">
          <h2>完了</h2>
          <p>完了しました。次のアクションへ進めます。</p>
          <p class="status">進捗: 3/3</p>
        </li>
      </ol>
      <div style="margin-top: 10px">
        <button type="button" data-back disabled>戻る</button>
        <button type="button" data-next>次へ</button>
        <button type="button" data-complete hidden>完了する</button>
      </div>
      <p role="status" data-status></p>
    </main>
    <script>
      (() => {
        const params = new URLSearchParams(window.location.search);
        const instance = params.get('instance') ?? 'missing-instance';
        const states = new Set(['step1', 'step2', 'done']);
        const order = ['step1', 'step2', 'done'];
        const steps = Array.from(document.querySelectorAll('[data-step]'));
        const status = document.querySelector('[data-status]');
        const backButton = document.querySelector('[data-back]');
        const nextButton = document.querySelector('[data-next]');
        const completeButton = document.querySelector('[data-complete]');
        const setTheme = (theme) => {
          if (theme === 'dark' || theme === 'light') {
            document.documentElement.dataset.theme = theme;
          }
        };
        const setState = (nextState) => {
          if (!states.has(nextState)) return;
          document.documentElement.dataset.state = nextState;
          const currentIndex = order.indexOf(nextState);
          for (const step of steps) {
            const index = order.indexOf(step.dataset.step);
            if (index === currentIndex) {
              step.setAttribute('aria-current', 'step');
            } else {
              step.removeAttribute('aria-current');
            }
            step.dataset.progress = index < currentIndex ? 'complete' : index === currentIndex ? 'current' : 'upcoming';
          }
          backButton.disabled = currentIndex === 0;
          nextButton.hidden = currentIndex !== 0;
          completeButton.hidden = currentIndex === 0;
          completeButton.disabled = currentIndex === order.length - 1;
          const currentStep = steps[currentIndex];
          if (status) status.textContent = `${currentStep.querySelector('h2')?.textContent ?? ''}の段階です。`;
        };
        backButton.addEventListener('click', () => {
          const currentIndex = order.indexOf(document.documentElement.dataset.state ?? 'step1');
          setState(order[Math.max(0, currentIndex - 1)]);
        });
        nextButton.addEventListener('click', () => setState('step2'));
        completeButton.addEventListener('click', () => setState('done'));
        window.addEventListener('message', (event) => {
          if (event.source !== window.parent || typeof event.data !== 'object' || event.data === null) return;
          if (event.data.source !== 'wpl-host' || event.data.instance !== instance) return;
          if (event.data.type === 'wpl-preview-state') setState(event.data.value);
          if (event.data.type === 'wpl-preview-theme') setTheme(event.data.value);
        });
        setTheme(params.get('theme') === 'dark' ? 'dark' : 'light');
        setState(states.has(params.get('state')) ? (params.get('state') ?? 'step1') : 'step1');
        window.parent.postMessage({ source: 'wpl-preview', type: 'wpl-preview-ready', instance }, '*');
      })();
    </script>
  </body>
</html>
プレビューで試せる動き
  • 開始: 開始前準備の案内を提示します。
  • 確認: 確認待ちの要件を示します。
  • 完了: 完了内容と次アクションを提示します。

使い分けを確認する

このレシピが合う場面

適する場面

  • 申込・レビュー・承認など段階型業務を可視化したい
  • 進捗と戻る操作が必要なフォームを設計する
  • 途中中断時の再開位置を明示したい

適さない場面

  • 単純な1段階の処理のみ
  • 非常に短い画面でステップ数を増やす必要がある場合