ナビゲーション
進行ステッパー
手順進捗を段階的に示し、戻る/進むや完了到達を確認できるステップ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段階の処理のみ
- 非常に短い画面でステップ数を増やす必要がある場合
