トグルスイッチ&セグメント切替
チェックボックスを土台にしたON/OFFスイッチと、インジケーターが動くピル型セグメント。設定画面や表示期間の切替に使えます。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk:通知設定トグル+請求期間のセグメント切替 -->
<div class="app">
<header class="app__bar">
<span class="app__logo">💼 FlowDesk</span>
<span class="app__tag">アカウント設定</span>
</header>
<main class="settings">
<h1 class="settings__h1">通知</h1>
<div class="row">
<div><p class="row__title">メール通知</p><p class="row__sub">タスクの更新をメールで受け取る</p></div>
<label class="switch"><input type="checkbox" checked><span class="switch__track"></span></label>
</div>
<div class="row">
<div><p class="row__title">デスクトップ通知</p><p class="row__sub">ブラウザにポップアップを表示</p></div>
<label class="switch"><input type="checkbox"><span class="switch__track"></span></label>
</div>
<div class="row">
<div><p class="row__title">週次レポート</p><p class="row__sub">毎週月曜にサマリーを送信</p></div>
<label class="switch"><input type="checkbox" checked><span class="switch__track"></span></label>
</div>
<h1 class="settings__h1">お支払いプラン</h1>
<!-- ピル型セグメント:インジケーターが動く -->
<div class="seg" data-seg>
<span class="seg__ink" aria-hidden="true"></span>
<button class="seg__btn is-active" data-plan="月払い">月払い</button>
<button class="seg__btn" data-plan="年払い">年払い <em>2ヶ月分お得</em></button>
</div>
<p class="settings__note">選択中:<strong data-plan-out>月払い</strong></p>
</main>
</div>
CSS
/* FlowDesk SaaS テーマ */
:root{--navy:#0f1b34;--blue:#4f7cff;--ink:#1d2740;--line:#e3e8f2;--muted:#6b7794;--bg:#f4f6fb}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;font-family:"Segoe UI",system-ui,sans-serif;background:var(--bg);color:var(--ink)}
.app{max-width:480px;margin:0 auto;min-height:100vh;display:flex;flex-direction:column}
.app__bar{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;background:var(--navy);color:#fff}
.app__logo{font-weight:700}
.app__tag{font-size:.78rem;color:#aab6d6}
.settings{flex:1;padding:18px 22px}
.settings__h1{margin:18px 0 10px;font-size:.78rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.settings__h1:first-child{margin-top:4px}
.row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:13px 0;border-bottom:1px solid var(--line)}
.row__title{margin:0;font-size:.92rem;font-weight:600}
.row__sub{margin:2px 0 0;font-size:.76rem;color:var(--muted)}
/* トグルスイッチ */
.switch{position:relative;flex:none;width:46px;height:26px;cursor:pointer}
.switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
.switch__track{position:absolute;inset:0;border-radius:999px;background:#cdd5e6;transition:background .25s}
.switch__track::after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:transform .25s}
.switch input:checked + .switch__track{background:var(--blue)}
.switch input:checked + .switch__track::after{transform:translateX(20px)}
.switch input:focus-visible + .switch__track{outline:2px solid var(--blue);outline-offset:2px}
/* セグメント切替 */
.seg{position:relative;display:flex;background:#e8ecf6;border-radius:12px;padding:4px;margin-bottom:10px}
.seg__ink{position:absolute;top:4px;left:4px;bottom:4px;width:calc(50% - 4px);background:#fff;border-radius:9px;box-shadow:0 2px 8px rgba(15,27,52,.12);transition:transform .28s cubic-bezier(.4,0,.2,1)}
.seg__btn{position:relative;z-index:1;flex:1;appearance:none;background:none;border:none;cursor:pointer;font:inherit;font-weight:600;font-size:.88rem;color:var(--muted);padding:9px 4px;transition:color .25s}
.seg__btn.is-active{color:var(--navy)}
.seg__btn em{font-style:normal;font-size:.66rem;color:var(--blue);margin-left:4px}
.settings__note{margin:0;font-size:.84rem;color:var(--muted)}
.settings__note strong{color:var(--navy)}
@media (prefers-reduced-motion:reduce){.switch__track,.switch__track::after,.seg__ink,.seg__btn{transition:none}}
JavaScript
// セグメント切替:インジケーターを選択ボタン側へスライド
const seg = document.querySelector('[data-seg]');
if (seg) {
const ink = seg.querySelector('.seg__ink');
const btns = [...seg.querySelectorAll('.seg__btn')];
const out = document.querySelector('[data-plan-out]');
btns.forEach((btn, i) => {
btn.addEventListener('click', () => {
btns.forEach((b) => b.classList.toggle('is-active', b === btn));
// 2分割なので index で 0% / 100% へ移動
if (ink) ink.style.transform = `translateX(${i * 100}%)`;
if (out) out.textContent = btn.dataset.plan;
});
});
}
コード
HTML
<!-- トグルスイッチ:チェックボックスを土台にした見栄えするON/OFF&セグメント切替 -->
<div class="sw">
<h2 class="sw__title">設定</h2>
<!-- ON/OFF スイッチ群 -->
<ul class="sw__list">
<li class="sw__row">
<span>ダークモード</span>
<label class="toggle">
<input type="checkbox" checked>
<span class="toggle__track"><span class="toggle__thumb"></span></span>
</label>
</li>
<li class="sw__row">
<span>プッシュ通知</span>
<label class="toggle">
<input type="checkbox">
<span class="toggle__track"><span class="toggle__thumb"></span></span>
</label>
</li>
<li class="sw__row">
<span>自動同期</span>
<label class="toggle">
<input type="checkbox" checked>
<span class="toggle__track"><span class="toggle__thumb"></span></span>
</label>
</li>
</ul>
<!-- セグメント(ピル型)切替 -->
<div class="seg" data-seg>
<span class="seg__ink" aria-hidden="true"></span>
<button class="seg__btn is-active">日</button>
<button class="seg__btn">週</button>
<button class="seg__btn">月</button>
</div>
</div>
CSS
:root{
--bg:#0f1422;
--card:#1a2236;
--on:#34d399;
--accent:#6366f1;
--text:#e6ecf7;
--muted:#8b97b3;
--off:#3a455f;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:26px 16px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:radial-gradient(700px 360px at 50% -10%,#202c4a,transparent),var(--bg);
}
.sw{
width:min(400px,100%);
background:var(--card);border:1px solid #28324c;border-radius:18px;
padding:24px 22px;
}
.sw__title{margin:0 0 16px;font-size:1.1rem}
.sw__list{list-style:none;margin:0 0 22px;padding:0;display:grid;gap:4px}
.sw__row{
display:flex;align-items:center;justify-content:space-between;
padding:11px 4px;font-size:.94rem;
border-bottom:1px solid #232c44;
}
.sw__row:last-child{border-bottom:none}
/* トグルスイッチ本体 */
.toggle{position:relative;display:inline-flex;cursor:pointer}
.toggle input{position:absolute;opacity:0;width:0;height:0}
.toggle__track{
width:48px;height:28px;border-radius:999px;
background:#3a455f;transition:background .3s;
display:flex;align-items:center;padding:3px;
}
.toggle__thumb{
width:22px;height:22px;border-radius:50%;background:#fff;
box-shadow:0 2px 6px rgba(0,0,0,.4);
transition:transform .3s cubic-bezier(.4,0,.2,1);
}
.toggle input:checked + .toggle__track{background:var(--on)}
.toggle input:checked + .toggle__track .toggle__thumb{transform:translateX(20px)}
.toggle input:focus-visible + .toggle__track{outline:2px solid var(--accent);outline-offset:2px}
/* セグメント切替 */
.seg{
position:relative;display:flex;gap:2px;
background:#222b42;border-radius:12px;padding:4px;
}
.seg__ink{
position:absolute;top:4px;bottom:4px;left:4px;width:0;
border-radius:9px;background:linear-gradient(135deg,var(--accent),#8b5cf6);
transition:left .3s cubic-bezier(.4,0,.2,1),width .3s cubic-bezier(.4,0,.2,1);
z-index:0;
}
.seg__btn{
position:relative;z-index:1;flex:1;
border:none;background:none;cursor:pointer;
font:inherit;font-weight:600;color:var(--muted);
padding:9px 0;transition:color .25s;
}
.seg__btn.is-active{color:#fff}
.seg__btn:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:9px}
@media (prefers-reduced-motion:reduce){
.toggle__thumb,.seg__ink{transition:none}
}
JavaScript
// セグメント切替:選択ボタンへスライドインジケーターを移動
const seg = document.querySelector('[data-seg]');
if (seg) {
const btns = [...seg.querySelectorAll('.seg__btn')];
const ink = seg.querySelector('.seg__ink');
// インジケーターを対象ボタン位置へ
const move = (btn) => {
if (!ink || !btn) return;
ink.style.left = btn.offsetLeft + 'px';
ink.style.width = btn.offsetWidth + 'px';
};
const select = (btn) => {
btns.forEach((b) => b.classList.toggle('is-active', b === btn));
move(btn);
};
btns.forEach((btn) => btn.addEventListener('click', () => select(btn)));
// 初期位置(レイアウト確定後)+リサイズ追従
const active = seg.querySelector('.seg__btn.is-active') || btns[0];
requestAnimationFrame(() => move(active));
window.addEventListener('resize', () => move(seg.querySelector('.seg__btn.is-active')));
}
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「トグルスイッチ&セグメント切替」の効果を追加してください。
# 追加してほしい効果
トグルスイッチ&セグメント切替(UIコンポーネント)
チェックボックスを土台にしたON/OFFスイッチと、インジケーターが動くピル型セグメント。設定画面や表示期間の切替に使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- トグルスイッチ:チェックボックスを土台にした見栄えするON/OFF&セグメント切替 -->
<div class="sw">
<h2 class="sw__title">設定</h2>
<!-- ON/OFF スイッチ群 -->
<ul class="sw__list">
<li class="sw__row">
<span>ダークモード</span>
<label class="toggle">
<input type="checkbox" checked>
<span class="toggle__track"><span class="toggle__thumb"></span></span>
</label>
</li>
<li class="sw__row">
<span>プッシュ通知</span>
<label class="toggle">
<input type="checkbox">
<span class="toggle__track"><span class="toggle__thumb"></span></span>
</label>
</li>
<li class="sw__row">
<span>自動同期</span>
<label class="toggle">
<input type="checkbox" checked>
<span class="toggle__track"><span class="toggle__thumb"></span></span>
</label>
</li>
</ul>
<!-- セグメント(ピル型)切替 -->
<div class="seg" data-seg>
<span class="seg__ink" aria-hidden="true"></span>
<button class="seg__btn is-active">日</button>
<button class="seg__btn">週</button>
<button class="seg__btn">月</button>
</div>
</div>
【CSS】
:root{
--bg:#0f1422;
--card:#1a2236;
--on:#34d399;
--accent:#6366f1;
--text:#e6ecf7;
--muted:#8b97b3;
--off:#3a455f;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:26px 16px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:radial-gradient(700px 360px at 50% -10%,#202c4a,transparent),var(--bg);
}
.sw{
width:min(400px,100%);
background:var(--card);border:1px solid #28324c;border-radius:18px;
padding:24px 22px;
}
.sw__title{margin:0 0 16px;font-size:1.1rem}
.sw__list{list-style:none;margin:0 0 22px;padding:0;display:grid;gap:4px}
.sw__row{
display:flex;align-items:center;justify-content:space-between;
padding:11px 4px;font-size:.94rem;
border-bottom:1px solid #232c44;
}
.sw__row:last-child{border-bottom:none}
/* トグルスイッチ本体 */
.toggle{position:relative;display:inline-flex;cursor:pointer}
.toggle input{position:absolute;opacity:0;width:0;height:0}
.toggle__track{
width:48px;height:28px;border-radius:999px;
background:#3a455f;transition:background .3s;
display:flex;align-items:center;padding:3px;
}
.toggle__thumb{
width:22px;height:22px;border-radius:50%;background:#fff;
box-shadow:0 2px 6px rgba(0,0,0,.4);
transition:transform .3s cubic-bezier(.4,0,.2,1);
}
.toggle input:checked + .toggle__track{background:var(--on)}
.toggle input:checked + .toggle__track .toggle__thumb{transform:translateX(20px)}
.toggle input:focus-visible + .toggle__track{outline:2px solid var(--accent);outline-offset:2px}
/* セグメント切替 */
.seg{
position:relative;display:flex;gap:2px;
background:#222b42;border-radius:12px;padding:4px;
}
.seg__ink{
position:absolute;top:4px;bottom:4px;left:4px;width:0;
border-radius:9px;background:linear-gradient(135deg,var(--accent),#8b5cf6);
transition:left .3s cubic-bezier(.4,0,.2,1),width .3s cubic-bezier(.4,0,.2,1);
z-index:0;
}
.seg__btn{
position:relative;z-index:1;flex:1;
border:none;background:none;cursor:pointer;
font:inherit;font-weight:600;color:var(--muted);
padding:9px 0;transition:color .25s;
}
.seg__btn.is-active{color:#fff}
.seg__btn:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:9px}
@media (prefers-reduced-motion:reduce){
.toggle__thumb,.seg__ink{transition:none}
}
【JavaScript】
// セグメント切替:選択ボタンへスライドインジケーターを移動
const seg = document.querySelector('[data-seg]');
if (seg) {
const btns = [...seg.querySelectorAll('.seg__btn')];
const ink = seg.querySelector('.seg__ink');
// インジケーターを対象ボタン位置へ
const move = (btn) => {
if (!ink || !btn) return;
ink.style.left = btn.offsetLeft + 'px';
ink.style.width = btn.offsetWidth + 'px';
};
const select = (btn) => {
btns.forEach((b) => b.classList.toggle('is-active', b === btn));
move(btn);
};
btns.forEach((btn) => btn.addEventListener('click', () => select(btn)));
// 初期位置(レイアウト確定後)+リサイズ追従
const active = seg.querySelector('.seg__btn.is-active') || btns[0];
requestAnimationFrame(() => move(active));
window.addEventListener('resize', () => move(seg.querySelector('.seg__btn.is-active')));
}
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。