ステップインジケータ
1→2→3→4の水平ステップを完了・現在・未完で色分けし、接続線が進むごとに満ちていく進行状況の表示。次へ/戻るで手動操作もできます。
ライブデモ
コード
HTML
<!-- ステップインジケータ: 4段階の進行を色分けと接続線の充填で表現し、次へ/戻るで操作できる -->
<div class="stage">
<div class="wizard">
<div class="wizard__steps" id="ssiSteps" role="progressbar" aria-valuemin="1" aria-valuemax="4" aria-valuenow="1" aria-valuetext="ステップ 1 / 4: アカウント">
<ol class="steps" aria-hidden="true">
<li class="step" data-step="1">
<span class="step__circle">
<span class="step__num">1</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">アカウント</span>
</li>
<li class="connector"><span class="connector__fill"></span></li>
<li class="step" data-step="2">
<span class="step__circle">
<span class="step__num">2</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">お届け先</span>
</li>
<li class="connector"><span class="connector__fill"></span></li>
<li class="step" data-step="3">
<span class="step__circle">
<span class="step__num">3</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">お支払い</span>
</li>
<li class="connector"><span class="connector__fill"></span></li>
<li class="step" data-step="4">
<span class="step__circle">
<span class="step__num">4</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">確認</span>
</li>
</ol>
</div>
<div class="wizard__actions">
<button type="button" class="btn btn--ghost" id="ssiBack">戻る</button>
<button type="button" class="btn btn--primary" id="ssiNext">次へ</button>
</div>
</div>
</div>
CSS
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 18% 18%, #1f2b3a 0%, transparent 50%),
radial-gradient(circle at 85% 85%, #262038 0%, transparent 52%),
#10131a;
color: #eef1f6;
}
.stage {
width: 100%;
display: grid;
place-items: center;
}
.wizard {
width: min(420px, 100%);
padding: 30px 24px 24px;
border-radius: 20px;
background: linear-gradient(165deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.015));
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
}
.steps {
list-style: none;
margin: 0;
padding: 0;
display: flex;
align-items: flex-start;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
flex: 0 0 auto;
width: 64px;
}
.step__circle {
position: relative;
width: 34px;
height: 34px;
border-radius: 50%;
display: grid;
place-items: center;
background: rgba(255, 255, 255, 0.06);
border: 2px solid rgba(255, 255, 255, 0.16);
font-size: 13px;
font-weight: 700;
color: rgba(238, 241, 246, 0.55);
transition: background-color 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease, transform 0.3s ease;
}
.step__num,
.step__check {
grid-area: 1 / 1;
transition: opacity 0.25s ease, transform 0.25s ease;
}
.step__check {
opacity: 0;
transform: scale(0.5);
color: #0c1a12;
font-size: 15px;
}
.step__label {
font-size: 11px;
letter-spacing: 0.02em;
color: rgba(238, 241, 246, 0.45);
text-align: center;
transition: color 0.4s ease;
}
.connector {
position: relative;
flex: 1 1 auto;
height: 34px;
display: flex;
align-items: center;
padding: 0 2px;
}
.connector::before {
content: "";
display: block;
width: 100%;
height: 3px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
}
.connector__fill {
position: absolute;
left: 2px;
right: 2px;
top: 50%;
height: 3px;
width: 0;
transform: translateY(-50%);
border-radius: 999px;
background: linear-gradient(90deg, #38bdf8, #34d399);
transition: width 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.connector.is-filled .connector__fill {
width: calc(100% - 4px);
}
/* 現在のステップ */
.step.is-current .step__circle {
background: rgba(56, 189, 248, 0.16);
border-color: #38bdf8;
color: #7dd3fc;
box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.14);
transform: scale(1.06);
}
.step.is-current .step__label {
color: #eef1f6;
font-weight: 600;
}
/* 完了したステップ */
.step.is-complete .step__circle {
background: linear-gradient(140deg, #34d399, #22c07a);
border-color: transparent;
}
.step.is-complete .step__num {
opacity: 0;
transform: scale(0.5);
}
.step.is-complete .step__check {
opacity: 1;
transform: scale(1);
}
.step.is-complete .step__label {
color: rgba(238, 241, 246, 0.75);
}
.wizard__actions {
display: flex;
justify-content: space-between;
gap: 12px;
margin-top: 26px;
}
.btn {
flex: 1 1 auto;
padding: 10px 16px;
font-size: 13px;
font-weight: 600;
border-radius: 12px;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.15s ease, background-color 0.2s ease, opacity 0.2s ease;
}
.btn:active {
transform: scale(0.97);
}
.btn--ghost {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.14);
color: rgba(238, 241, 246, 0.75);
}
.btn--ghost:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.btn--primary {
background: linear-gradient(135deg, #38bdf8, #34d399);
color: #0b1220;
}
@media (max-width: 380px) {
.wizard { padding: 24px 16px 18px; }
.step { width: 52px; }
}
JavaScript
// ステップインジケータ: 現在地を色分けし、接続線を充填する。自動進行とNext/Backの手動操作を両立させる
(() => {
const root = document.getElementById('ssiSteps');
const backBtn = document.getElementById('ssiBack');
const nextBtn = document.getElementById('ssiNext');
if (!root) return; // null安全
const stepsEls = Array.from(root.querySelectorAll('.step'));
const connectors = Array.from(root.querySelectorAll('.connector'));
if (!stepsEls.length) return;
const TOTAL = stepsEls.length;
const LABELS = stepsEls.map((el) => el.querySelector('.step__label')?.textContent?.trim() || '');
const AUTO_MS = 2400;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let current = 1;
let autoTimer = null;
function render() {
stepsEls.forEach((el, i) => {
const n = i + 1;
el.classList.toggle('is-complete', n < current);
el.classList.toggle('is-current', n === current);
el.classList.toggle('is-upcoming', n > current);
});
connectors.forEach((el, i) => {
el.classList.toggle('is-filled', i + 1 < current);
});
root.setAttribute('aria-valuenow', String(current));
root.setAttribute('aria-valuetext', `ステップ ${current} / ${TOTAL}: ${LABELS[current - 1]}`);
if (backBtn) backBtn.disabled = current === 1;
if (nextBtn) nextBtn.textContent = current === TOTAL ? '完了' : '次へ';
}
function goTo(n) {
// 1〜TOTALの範囲でループさせる(完了の次はアカウントへ)
current = ((n - 1 + TOTAL) % TOTAL) + 1;
render();
}
function restartAuto() {
if (reduce) return;
clearInterval(autoTimer);
autoTimer = setInterval(() => goTo(current + 1), AUTO_MS);
}
nextBtn?.addEventListener('click', () => {
goTo(current + 1);
restartAuto();
});
backBtn?.addEventListener('click', () => {
goTo(current - 1);
restartAuto();
});
render();
restartAuto();
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ステップインジケータ」の効果を追加してください。
# 追加してほしい効果
ステップインジケータ(カレンダー & ステップ)
1→2→3→4の水平ステップを完了・現在・未完で色分けし、接続線が進むごとに満ちていく進行状況の表示。次へ/戻るで手動操作もできます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ステップインジケータ: 4段階の進行を色分けと接続線の充填で表現し、次へ/戻るで操作できる -->
<div class="stage">
<div class="wizard">
<div class="wizard__steps" id="ssiSteps" role="progressbar" aria-valuemin="1" aria-valuemax="4" aria-valuenow="1" aria-valuetext="ステップ 1 / 4: アカウント">
<ol class="steps" aria-hidden="true">
<li class="step" data-step="1">
<span class="step__circle">
<span class="step__num">1</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">アカウント</span>
</li>
<li class="connector"><span class="connector__fill"></span></li>
<li class="step" data-step="2">
<span class="step__circle">
<span class="step__num">2</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">お届け先</span>
</li>
<li class="connector"><span class="connector__fill"></span></li>
<li class="step" data-step="3">
<span class="step__circle">
<span class="step__num">3</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">お支払い</span>
</li>
<li class="connector"><span class="connector__fill"></span></li>
<li class="step" data-step="4">
<span class="step__circle">
<span class="step__num">4</span>
<span class="step__check" aria-hidden="true">✓</span>
</span>
<span class="step__label">確認</span>
</li>
</ol>
</div>
<div class="wizard__actions">
<button type="button" class="btn btn--ghost" id="ssiBack">戻る</button>
<button type="button" class="btn btn--primary" id="ssiNext">次へ</button>
</div>
</div>
</div>
【CSS】
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 18% 18%, #1f2b3a 0%, transparent 50%),
radial-gradient(circle at 85% 85%, #262038 0%, transparent 52%),
#10131a;
color: #eef1f6;
}
.stage {
width: 100%;
display: grid;
place-items: center;
}
.wizard {
width: min(420px, 100%);
padding: 30px 24px 24px;
border-radius: 20px;
background: linear-gradient(165deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.015));
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
}
.steps {
list-style: none;
margin: 0;
padding: 0;
display: flex;
align-items: flex-start;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
flex: 0 0 auto;
width: 64px;
}
.step__circle {
position: relative;
width: 34px;
height: 34px;
border-radius: 50%;
display: grid;
place-items: center;
background: rgba(255, 255, 255, 0.06);
border: 2px solid rgba(255, 255, 255, 0.16);
font-size: 13px;
font-weight: 700;
color: rgba(238, 241, 246, 0.55);
transition: background-color 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease, transform 0.3s ease;
}
.step__num,
.step__check {
grid-area: 1 / 1;
transition: opacity 0.25s ease, transform 0.25s ease;
}
.step__check {
opacity: 0;
transform: scale(0.5);
color: #0c1a12;
font-size: 15px;
}
.step__label {
font-size: 11px;
letter-spacing: 0.02em;
color: rgba(238, 241, 246, 0.45);
text-align: center;
transition: color 0.4s ease;
}
.connector {
position: relative;
flex: 1 1 auto;
height: 34px;
display: flex;
align-items: center;
padding: 0 2px;
}
.connector::before {
content: "";
display: block;
width: 100%;
height: 3px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
}
.connector__fill {
position: absolute;
left: 2px;
right: 2px;
top: 50%;
height: 3px;
width: 0;
transform: translateY(-50%);
border-radius: 999px;
background: linear-gradient(90deg, #38bdf8, #34d399);
transition: width 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.connector.is-filled .connector__fill {
width: calc(100% - 4px);
}
/* 現在のステップ */
.step.is-current .step__circle {
background: rgba(56, 189, 248, 0.16);
border-color: #38bdf8;
color: #7dd3fc;
box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.14);
transform: scale(1.06);
}
.step.is-current .step__label {
color: #eef1f6;
font-weight: 600;
}
/* 完了したステップ */
.step.is-complete .step__circle {
background: linear-gradient(140deg, #34d399, #22c07a);
border-color: transparent;
}
.step.is-complete .step__num {
opacity: 0;
transform: scale(0.5);
}
.step.is-complete .step__check {
opacity: 1;
transform: scale(1);
}
.step.is-complete .step__label {
color: rgba(238, 241, 246, 0.75);
}
.wizard__actions {
display: flex;
justify-content: space-between;
gap: 12px;
margin-top: 26px;
}
.btn {
flex: 1 1 auto;
padding: 10px 16px;
font-size: 13px;
font-weight: 600;
border-radius: 12px;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.15s ease, background-color 0.2s ease, opacity 0.2s ease;
}
.btn:active {
transform: scale(0.97);
}
.btn--ghost {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.14);
color: rgba(238, 241, 246, 0.75);
}
.btn--ghost:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.btn--primary {
background: linear-gradient(135deg, #38bdf8, #34d399);
color: #0b1220;
}
@media (max-width: 380px) {
.wizard { padding: 24px 16px 18px; }
.step { width: 52px; }
}
【JavaScript】
// ステップインジケータ: 現在地を色分けし、接続線を充填する。自動進行とNext/Backの手動操作を両立させる
(() => {
const root = document.getElementById('ssiSteps');
const backBtn = document.getElementById('ssiBack');
const nextBtn = document.getElementById('ssiNext');
if (!root) return; // null安全
const stepsEls = Array.from(root.querySelectorAll('.step'));
const connectors = Array.from(root.querySelectorAll('.connector'));
if (!stepsEls.length) return;
const TOTAL = stepsEls.length;
const LABELS = stepsEls.map((el) => el.querySelector('.step__label')?.textContent?.trim() || '');
const AUTO_MS = 2400;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let current = 1;
let autoTimer = null;
function render() {
stepsEls.forEach((el, i) => {
const n = i + 1;
el.classList.toggle('is-complete', n < current);
el.classList.toggle('is-current', n === current);
el.classList.toggle('is-upcoming', n > current);
});
connectors.forEach((el, i) => {
el.classList.toggle('is-filled', i + 1 < current);
});
root.setAttribute('aria-valuenow', String(current));
root.setAttribute('aria-valuetext', `ステップ ${current} / ${TOTAL}: ${LABELS[current - 1]}`);
if (backBtn) backBtn.disabled = current === 1;
if (nextBtn) nextBtn.textContent = current === TOTAL ? '完了' : '次へ';
}
function goTo(n) {
// 1〜TOTALの範囲でループさせる(完了の次はアカウントへ)
current = ((n - 1 + TOTAL) % TOTAL) + 1;
render();
}
function restartAuto() {
if (reduce) return;
clearInterval(autoTimer);
autoTimer = setInterval(() => goTo(current + 1), AUTO_MS);
}
nextBtn?.addEventListener('click', () => {
goTo(current + 1);
restartAuto();
});
backBtn?.addEventListener('click', () => {
goTo(current - 1);
restartAuto();
});
render();
restartAuto();
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。