GODRICH

セグメントコントロールUI 9選 — コピペOK

セグメントコントロールとは、iOSの設定画面でよく見る、いくつかの選択肢から一つを選ぶと背景のピルがその位置までスライドするタブ切り替えボタンです。セグメントコントロールから絵文字リアクション・カラースウォッチまで、名前は知らなくてもアプリで見たことがある選択UI9種をコードごとまとめました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つは人気順ではなく、一つの画面で出会う順番で並べました。01・02はいくつかの表示から一つを選ぶセグメントコントロール、03・04はチップを押して選ぶ場合、05・06は絵文字でリアクションを残す場合、07は色、08は時刻、09は並び替え基準を選ぶ場合です。

01iOSピルセグメントコントロール

均等幅の3つのラジオの上を、青いピル背景がtranslateXで選ばれたラベルの位置までスライドします。設定画面の表示モード切り替えや、注文状況フィルター(すべて/進行中/完了)に使えます。

translateX(N*100%)균등폭 flex0.22초
.seg__pill {
  position: absolute;
  width: calc((100% - 8px) / 3);
  transform: translateX(0);
  transition: transform $duration $easing;
}
#seg1-2:checked ~ .seg__track .seg__pill { transform: translateX(100%); }
#seg1-3:checked ~ .seg__track .seg__pill { transform: translateX(200%); }

02アイコン2択セグメント

リスト・グリッドのどちらかを選ぶと、ピル背景がトラック幅の半分だけtranslateXで移動し、選んだアイコンだけが濃い色になります。掲示板のリスト/グリッド表示切替でよく見る動きです。

translateX 50%aria-label0.18초
.iv__pill {
  position: absolute;
  width: $sp-12;
  transform: translateX(0);
  transition: transform $duration $easing;
}
#iv1-2:checked ~ .iv__track .iv__pill { transform: translateX(100%); }

03複数選択フィルターチップ

複数のチェックボックスをチップ状に並べ、選んだチップだけ背景が中央からscaleXで広がり、インラインSVGのチェックアイコンがscale(0)から1で現れます。送料無料・当日発送・割引のようなECサイトのフィルターに向いています。

scaleX 채움:has() 선택자0.2초
.cf__chip::before {
  content: "";
  position: absolute; inset: 0;
  background: $color;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform $duration $easing;
}
.cf__chip:has(.cf__input:checked)::before { transform: scaleX(1); }

04サイズラジオチップ

S・M・L・XLのラジオチップから一つを選ぶと、そのチップだけscale(.84)に軽く沈んでから弾み戻り、枠線が太くなります。商品サイズ選択や予約人数選択に使われるパターンです。

scale 바운스cubic-bezier 오버슈트0.3초
.sz__chip:has(.sz__input:checked) {
  border-color: $color;
  border-width: 3px;
  animation: sz-press $duration $easing;
}
@keyframes sz-press {
  0%   { transform: scale(1); }
  35%  { transform: scale(.84); }
  70%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}

05絵文字リアクションポップアップ

リアクションボタンを押すと絵文字4つの帯がopacitytranslateYで浮かび上がり、ボタンのaria-expandedがリアルタイムで更新されます。メッセージへのリアクションや投稿への反応に使う仕組みです。

opacity+translateYaria-expandedEsc 로 닫힘
.rp__pop {
  transform: translate(-50%, 8px) scale(.85);
  opacity: 0;
  pointer-events: none;
  transition: transform $duration $easing, opacity $duration $easing;
}
.rp.is-open .rp__pop {
  transform: translate(-50%, 0) scale(1);
  opacity: 1;
  pointer-events: auto;
}

06絵文字バーストカウント

絵文字を選ぶとその絵文字がscaleで大きく弾み、周りの点4つが外側に散って消える間に、隣のバッジの数字が1つ増えます。ショート動画のハートリアクションと同じ仕掛けです。

scale 바운스burst dot ×4aria-live
.bc.is-play .bc__emoji { animation: bc-emoji $duration $easing; }
.bc.is-play .bc__burst { animation: bc-burst $duration $easing; }
@keyframes bc-emoji {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.5); }
  60%  { transform: scale(.9); }
  100% { transform: scale(1); }
}

07グリッドカラースウォッチ

6色のスウォッチを3×2グリッドに並べ、一つを選ぶとその右下に小さなチェックバッジがscale(0)から1で現れます。商品カラーオプションのグリッドやテーマカラー選択画面に合います。

scale(0)→scale(1) 체크3×2 grid0.22초
.cw__check {
  transform: scale(0);
  transition: transform $duration $easing;
}
.cw__check-icon {          /* 인라인 Phosphor SVG — currentColor 를 물려받는다 */
  position: absolute; inset: 3px;
  color: $color;
}
.cw__cell:has(.cw__input:checked) .cw__check { transform: scale(1); }

08オドメーター式時刻スクラバー

時・分の上下ボタンを押すと現在の数字がtranslateYで押し出され、次の数字がその場所に滑り込む、車の走行距離計のような動きです。予約時間の選択やアラーム時刻の設定に使えます。

translateY 롤오버data-dir 버튼0.18초
function roll(col, dir) {
  var win = col.querySelector('.tp__win');
  var cur = win.querySelector('.is-cur'), next = win.querySelector('.is-next');
  var v = (parseInt(cur.textContent, 10) + dir + 60) % 60;
  next.textContent = String(v).padStart(2, '0');
  cur.classList.add('is-leaving');
  next.classList.add('is-arriving');
}

09チェックマーク並び替えドロップダウン

並び替えボタンを押すとリストがopacitytranslateYで開き、選んだ項目だけにチェックマークが付き、トリガーの文字もその項目名に変わります。商品一覧の並び替えや掲示板の投稿並び替えに使います。

role=menuopacity+translateY0.2초
.sd__menu {
  transform: translate(-50%, -6px);
  opacity: 0;
  transition: transform $duration $easing, opacity $duration $easing;
}
.sd.is-open .sd__menu { transform: translate(-50%, 0); opacity: 1; }
.sd__check {               /* 인라인 Phosphor SVG 체크 */
  color: $color;
  transform: scale(0);
  transition: transform $dur-instant $easing;
}
.sd__item.is-checked .sd__check { transform: scale(1); }

どこで壊れるか — 隠したinputと:has()の落とし穴

01・02・03・04・07はラジオ・チェックボックスの本物の<input>position: absolute; width: 1px; height: 1px; clip: rect(0,0,0,0);で見た目だけ隠しています。display: nonevisibility: hiddenを使うとフォーカス順序から完全に外れ、キーボードだけで操作する人はTabキーでたどり着けなくなります。03・07が使う:has()Chrome・Edge 105+、Safari 15.4+、Firefox 121+でのみ動作し、それより古いバージョンではチェック表示が出ないだけでクリック自体は問題なく動きます。08のオドメーターの動きはCSSだけでは2桁の数字を同時に押し出し滑り込ませるのが難しく、roll()という12行のJavaScriptがタイミングを合わせています。9つのファイルがそのまま入ったzipの圧縮パスワードはe9nu36x4で、05・09のようにボタンで開くポップアップは外側クリックやEscでも閉じるべきですが、このデモは分量を抑えるためEscのみ実装し、外側クリックでの終了は省いています — 実際に使う際はdocumentへのクリックリスナーを追加するのが安全です。

アクセシビリティ

01・02・03・04・07は本物の<input>を隠しつつ残しているため、スクリーンリーダーはラジオ・チェックボックスをそのまま読み上げ、Tabとスペース・矢印キーだけで状態を変えられます。05・09のポップアップ・メニューのトリガーは開閉のたびにaria-expandedを更新し、09の各項目はrole="menuitemradio"aria-checkedで現在の選択を伝えます。06のカウントはaria-live="polite"なので、反応数が変わるとスクリーンリーダーが静かに新しい数字を読み上げます。prefers-reduced-motion: reduceでは9つ全部でトランジション・アニメーションを止めますが、ピル・チェック・メニューは行き着いた状態のまま残るため、動きを消しても状態の情報自体は消えません。

@media (prefers-reduced-motion: reduce) {
  .seg__pill, .cf__chip::before, .cw__check { transition: none; }
  .bc.is-play .bc__emoji, .bc.is-play .bc__burst { animation: none !important; }
}

選択UI以外のCSSアニメーションは実用UIカテゴリーにもあり、このサイトがどんな基準でデモを検証しているかは紹介ページにまとめています。

FAQ

セグメントコントロールとタブは何が違いますか?

タブは押すと別のページやコンテンツ領域に移動することが多いですが、セグメントコントロールは同じ画面の中でフィルターや表示モードのような値を一つだけ変えます。01・02がページ移動なしで即座に状態だけ変わる理由です。

:has()が使えないブラウザでは03・04・07は完全に壊れますか?

いいえ。:has()がなくてもチェックマークやリングが表示されないだけで、本物の<input>:checked状態やフォームの送信値は普通に動作します。見た目のフィードバックだけが失われます。

React版の09もEscで閉じますか?

はい。react/09-sort-dropdown-checkmark-menuのコンポーネントにも同じonKeyDownリスナーが入っており、Escを押すとopenfalseになってメニューが閉じます。外側クリックで閉じる処理は落とし穴の節で書いた通りこちらにも入っていないので、実際の運用では追加をおすすめします。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。