ボタン 中級

セグメント切替

週・月・年のようなピル状の3択切替。クリックすると白い選択インジケータが滑らかにスライドして移動し、aria-pressedも排他的に更新されます。矢印キーでも操作できます。

#css#js#button#toggle

ライブデモ

コード

HTML
<!-- セグメント切替ボタン: ピル状の3択でインジケータが選択位置へ滑らかに移動する -->
<div class="stage">
  <div class="btn-segment" role="group" aria-label="表示期間の切り替え">
    <span class="btn-segment__thumb" aria-hidden="true"></span>
    <button class="btn-segment__item" type="button" data-index="0" aria-pressed="true">週間</button>
    <button class="btn-segment__item" type="button" data-index="1" aria-pressed="false">月間</button>
    <button class="btn-segment__item" type="button" data-index="2" aria-pressed="false">年間</button>
  </div>
  <p class="hint">クリックで選択が切り替わり、インジケータが滑らかに移動します(矢印キーでも操作可)</p>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  overflow: hidden;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 20%, #f3f6fb 0%, #e9edf7 50%, #dde3f2 100%);
}

.stage {
  display: grid;
  place-items: center;
  gap: 22px;
  padding: 24px;
}

/* --i: 選択中インデックス(0始まり)。JSがクリック/矢印キーで書き換える */
.btn-segment {
  --i: 0;
  position: relative;
  display: inline-flex;
  padding: 5px;
  border-radius: 999px;
  background: rgba(30, 41, 71, .08);
  box-shadow: inset 0 1px 3px rgba(30, 41, 71, .12);
  /* 待機時のアイドル: 外側にごく淡い影が呼吸してクリック可能なことを伝える */
  animation: segment-idle-breathe 3.4s ease-in-out infinite;
}

@keyframes segment-idle-breathe {
  0%, 100% { box-shadow: inset 0 1px 3px rgba(30, 41, 71, .12); }
  50%      { box-shadow: inset 0 1px 3px rgba(30, 41, 71, .12), 0 6px 16px rgba(59, 91, 219, .16); }
}

.btn-segment__thumb {
  position: absolute;
  top: 5px;
  left: 5px;
  bottom: 5px;
  width: calc((100% - 10px) / 3);
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 3px 10px rgba(30, 41, 71, .22);
  transform: translateX(calc(var(--i) * 100%));
  transition: transform .4s cubic-bezier(.22, 1, .36, 1);
}

.btn-segment__item {
  position: relative;
  z-index: 1;
  min-width: 84px;
  border: none;
  background: transparent;
  padding: 11px 20px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .02em;
  color: #5b6478;
  cursor: pointer;
  border-radius: 999px;
  -webkit-tap-highlight-color: transparent;
  transition: color .3s ease;
}

.btn-segment__item:hover {
  color: #34405c;
}

.btn-segment__item[aria-pressed="true"] {
  color: #1f2a4d;
}

.btn-segment__item:focus-visible {
  outline: 3px solid rgba(59, 91, 219, .5);
  outline-offset: 3px;
}

.hint {
  margin: 0;
  font-size: 13px;
  color: #5b6478;
  text-align: center;
  max-width: 280px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-segment { animation: none; }
  .btn-segment__thumb { transition-duration: .01ms; }
}
JavaScript
// セグメント切替ボタン: クリックで選択インデックスを --i に反映し、aria-pressedを排他的に更新する
(() => {
  const group = document.querySelector('.btn-segment');
  if (!group) return; // null安全

  const items = Array.from(group.querySelectorAll('.btn-segment__item'));
  if (!items.length) return;

  const select = (index) => {
    group.style.setProperty('--i', String(index));
    items.forEach((el, i) => {
      el.setAttribute('aria-pressed', String(i === index));
    });
  };

  items.forEach((el) => {
    el.addEventListener('click', () => {
      const index = Number(el.dataset.index || 0);
      select(index);
    });
  });

  // 左右矢印キーでの選択移動もサポート
  group.addEventListener('keydown', (e) => {
    if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
    const current = items.findIndex((el) => el.getAttribute('aria-pressed') === 'true');
    if (current === -1) return;
    const dir = e.key === 'ArrowRight' ? 1 : -1;
    const next = (current + dir + items.length) % items.length;
    select(next);
    items[next].focus();
    e.preventDefault();
  });
})();

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

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「セグメント切替」の効果を追加してください。

# 追加してほしい効果
セグメント切替(ボタン)
週・月・年のようなピル状の3択切替。クリックすると白い選択インジケータが滑らかにスライドして移動し、aria-pressedも排他的に更新されます。矢印キーでも操作できます。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- セグメント切替ボタン: ピル状の3択でインジケータが選択位置へ滑らかに移動する -->
<div class="stage">
  <div class="btn-segment" role="group" aria-label="表示期間の切り替え">
    <span class="btn-segment__thumb" aria-hidden="true"></span>
    <button class="btn-segment__item" type="button" data-index="0" aria-pressed="true">週間</button>
    <button class="btn-segment__item" type="button" data-index="1" aria-pressed="false">月間</button>
    <button class="btn-segment__item" type="button" data-index="2" aria-pressed="false">年間</button>
  </div>
  <p class="hint">クリックで選択が切り替わり、インジケータが滑らかに移動します(矢印キーでも操作可)</p>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  overflow: hidden;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 20%, #f3f6fb 0%, #e9edf7 50%, #dde3f2 100%);
}

.stage {
  display: grid;
  place-items: center;
  gap: 22px;
  padding: 24px;
}

/* --i: 選択中インデックス(0始まり)。JSがクリック/矢印キーで書き換える */
.btn-segment {
  --i: 0;
  position: relative;
  display: inline-flex;
  padding: 5px;
  border-radius: 999px;
  background: rgba(30, 41, 71, .08);
  box-shadow: inset 0 1px 3px rgba(30, 41, 71, .12);
  /* 待機時のアイドル: 外側にごく淡い影が呼吸してクリック可能なことを伝える */
  animation: segment-idle-breathe 3.4s ease-in-out infinite;
}

@keyframes segment-idle-breathe {
  0%, 100% { box-shadow: inset 0 1px 3px rgba(30, 41, 71, .12); }
  50%      { box-shadow: inset 0 1px 3px rgba(30, 41, 71, .12), 0 6px 16px rgba(59, 91, 219, .16); }
}

.btn-segment__thumb {
  position: absolute;
  top: 5px;
  left: 5px;
  bottom: 5px;
  width: calc((100% - 10px) / 3);
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 3px 10px rgba(30, 41, 71, .22);
  transform: translateX(calc(var(--i) * 100%));
  transition: transform .4s cubic-bezier(.22, 1, .36, 1);
}

.btn-segment__item {
  position: relative;
  z-index: 1;
  min-width: 84px;
  border: none;
  background: transparent;
  padding: 11px 20px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .02em;
  color: #5b6478;
  cursor: pointer;
  border-radius: 999px;
  -webkit-tap-highlight-color: transparent;
  transition: color .3s ease;
}

.btn-segment__item:hover {
  color: #34405c;
}

.btn-segment__item[aria-pressed="true"] {
  color: #1f2a4d;
}

.btn-segment__item:focus-visible {
  outline: 3px solid rgba(59, 91, 219, .5);
  outline-offset: 3px;
}

.hint {
  margin: 0;
  font-size: 13px;
  color: #5b6478;
  text-align: center;
  max-width: 280px;
}

@media (prefers-reduced-motion: reduce) {
  .btn-segment { animation: none; }
  .btn-segment__thumb { transition-duration: .01ms; }
}

【JavaScript】
// セグメント切替ボタン: クリックで選択インデックスを --i に反映し、aria-pressedを排他的に更新する
(() => {
  const group = document.querySelector('.btn-segment');
  if (!group) return; // null安全

  const items = Array.from(group.querySelectorAll('.btn-segment__item'));
  if (!items.length) return;

  const select = (index) => {
    group.style.setProperty('--i', String(index));
    items.forEach((el, i) => {
      el.setAttribute('aria-pressed', String(i === index));
    });
  };

  items.forEach((el) => {
    el.addEventListener('click', () => {
      const index = Number(el.dataset.index || 0);
      select(index);
    });
  });

  // 左右矢印キーでの選択移動もサポート
  group.addEventListener('keydown', (e) => {
    if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
    const current = items.findIndex((el) => el.getAttribute('aria-pressed') === 'true');
    if (current === -1) return;
    const dir = e.key === 'ArrowRight' ? 1 : -1;
    const next = (current + dir + items.length) % items.length;
    select(next);
    items[next].focus();
    e.preventDefault();
  });
})();

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

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