GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

CSS トグルスイッチ 9選 — 一行でコピペOK

css トグルスイッチは、チェックボックス一つで実際にオン・オフができ、トラックとノブの位置・色・アイコンだけを変えて見た目を作るコントロールです。ここでは9種類を集め、どれもtransformとopacityを軸にしたコードで組みました。

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

9つの トグルスイッチ html css は飾りの派手さではなく、トラックの上で何が変わるかという軸で並べています。ノブの位置だけが動く基本形(01・02)から、トラックの中身自体が変わるもの(03・04)、押し込みや塗りつぶしのような演出が加わるもの(05〜07)を経て、最後は構造が特殊だったり触れなくなったりする2つ(08・09)で締めくくりました。グリッドの各カードには.is-demoという自動ループ用のクラスが付いていて、実際に押すとpointerdownイベントでこのクラスが外れ、以降は本物のチェックボックスとして:checkedだけで動くようになります。03のON・OFFの文字にも色を流したいときはCSS テキスト グラデーション アニメーション、同じ<input>のままチェックボックスやラジオまで整えたいときはラジオボタン チェックボックス css カスタムアニメーションが続きになります。

01基本スライド

css スイッチボタン の基本形

チェックボックスにcheckedが付くと丸いノブがトラック右端まで滑り、下に重ねた2枚の背景レイヤーがopacityで入れ替わって、背景色も一緒に切り替わります。到着直前にノブが目標を少し行き過ぎてから戻るスプリングオーバーシュートと一瞬つぶれるsquashが加わり、着地の瞬間トラックの影が深くなってから浅く戻ります。

どこで使うか

設定画面のオプションスイッチや、フォームのチェックボックスをそのまま置き換えたい場所に向いています。

基本スライド
新しいタブで開く

01 · 基本スライド — 計測 累積面積 0.917% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translateX:checked0.25초
.switch__bg--off { background: rgba(255, 255, 255, .18); opacity: 1; }
.switch__bg--on  { background: $color; opacity: 0; }
.switch__knob { transform: translateX(0); transition: transform $duration $easing; }

.switch__input:checked ~ .switch__track .switch__bg--off { opacity: 0; }
.switch__input:checked ~ .switch__track .switch__bg--on  { opacity: 1; }
.switch__input:checked ~ .switch__track .switch__knob    { transform: translateX(24px); }

02iOS風トグル

どう動くか

丸いトラックと白いノブの組み合わせで、ノブが着地する瞬間だけ38%地点で一度オーバーシュートしてから止まるスプリング系のイージングを使っています。跳ねる瞬間ノブが縦に少しつぶれるsquashも重なり、トラックの影がノブが乗っているときは深く、離れると浅く変化します。

どこで使うか

iOSの設定アプリのように、指先の反応感が伝わってほしい画面に向いています。

iOS風トグル
新しいタブで開く

02 · iOS風トグル — 計測 累積面積 1.118% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

cubic-bezier 스프링그림자0.3초
@keyframes knob-loop {
  0%, 15%   { transform: translateX(0); }
  38%       { transform: translateX(28px); }   // スプリングのオーバーシュート
  40%, 60%  { transform: translateX(24px); }
  85%, 100% { transform: translateX(0); }
}

03ON/OFFラベル内蔵

どう動くか

トラックの両端にON・OFFの文字をあらかじめ固定しておき、ノブが移動する側の文字だけをz-indexで覆い隠します。移動の終わりでノブが一度弾んでからつぶれるスプリング+squashの締めが付き、文字を覆う瞬間トラックの影が深まって押さえ込む感触を出します。

どこで使うか

管理画面のステータス表示のように、今の値が色だけでなく文字でも分かってほしいスイッチに向いています。

ON/OFFラベル内蔵
新しいタブで開く

03 · ON/OFFラベル内蔵 — 計測 累積面積 1.507% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

z-indexoverflow:hidden0.25초
.switch__text--on  { left: 10px; }
.switch__text--off { right: 10px; }
.switch__knob {
  position: absolute; top: 3px; left: 3px; z-index: 1;
  transition: transform $duration $easing;
}
.switch__input:checked ~ .switch__track .switch__knob {
  transform: translateX(44px);
}

04アイコン切替

どう動くか

ノブが横に移動する間に、内側の×アイコンとチェックアイコンがopacityでクロスフェードして入れ替わります。移動先を少し行き過ぎてから戻るオーバーシュートと一瞬のsquashが重なり、切り替わる瞬間トラックの影が深くなります。

どこで使うか

権限の許可・拒否のように、結果をアイコン一つで言い切りたいスイッチに向いています。

アイコン切替
新しいタブで開く

04 · アイコン切替 — 計測 累積面積 1.05% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

opacity 크로스페이드SVG 아이콘0.2초
.switch__knob { transform: translateX(0); transition: transform $duration $easing; }
.switch__icon--x     { opacity: 1; }
.switch__icon--check { opacity: 0; }

.switch__input:checked ~ .switch__track .switch__knob        { transform: translateX(26px); }
.switch__input:checked ~ .switch__track .switch__icon--x     { opacity: 0; }
.switch__input:checked ~ .switch__track .switch__icon--check { opacity: 1; }

05押し込みスクイッシュ

どう動くか

CSSカスタムプロパティの--sx・--syを使い、押している間だけノブが横に潰れて楕円になり、離すと元の円に跳ね返ります。跳ね返る際に目標サイズを少し超えてから収まるスプリングが乗り、押している間深かったトラックの影が離した瞬間に浅く戻ります。

どこで使うか

タッチ操作中心のモバイルフォームで、押した手応えを画面側から返したいときに使います。

押し込みスクイッシュ
新しいタブで開く

05 · 押し込みスクイッシュ — 計測 累積面積 0.917% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scaleX:active0.15초
.switch__knob {
  --x: 0px; --sx: 1; --sy: 1;
  transform: translateX(var(--x)) scaleX(var(--sx)) scaleY(var(--sy));
  transition: transform $duration $easing;
}
.switch__input:checked ~ .switch__track .switch__knob { --x: 24px; }
.switch:active .switch__knob { --sx: 1.35; --sy: .8; }

06背景色の塗りつぶし

どう動くか

トラックの色は一度に切り替わらず、ノブが通った位置を起点にclip-pathの円がだんだん広がって塗りつぶします。塗り終わる瞬間ノブが一度弾んでつぶれるsquashで完了を知らせ、トラックの影も浅い状態から深い状態へ移ります。

どこで使うか

プレミアムプランへの切り替えのように、変化そのものを見せ場にしたいカードに向いています。

背景色の塗りつぶし
新しいタブで開く

06 · 背景色の塗りつぶし — 計測 累積面積 1.033% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-path:circle()번짐0.4초
.switch__fill {
  background: $color;
  clip-path: circle(0% at 18% 50%);
  transition: clip-path $duration $easing;
}
.switch__input:checked ~ .switch__track .switch__fill {
  clip-path: circle(75% at 82% 50%);
}

07昼夜アイコントグル

どう動くか

ノブが乗るトラック自体が空になっていて、背景グラデーションが昼の色から夜の色へ移り、小さな星がopacityで浮かび上がります。夜に切り替わる瞬間ノブが月の形へオーバーシュートしてからつぶれて収まり、昼の間深かったトラックの影は夜になると薄くなります。

どこで使うか

アイコン一つよりも大きな見せ場にしたいテーマ切り替えスイッチに向いています。

昼夜アイコントグル
新しいタブで開く

07 · 昼夜アイコントグル — 計測 累積面積 1.343% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

background-position별 opacity0.5초
.switch__track {
  background-image: linear-gradient(90deg, $stage-yellow 0%, $stage-yellow 50%, $color 50%, $color 100%);
  background-size: 200% 100%;
  background-position: 0% 0%;
  transition: background-position $duration $easing;
}
.switch__input:checked ~ .switch__track { background-position: 100% 0%; }
.switch__input:checked ~ .switch__track .switch__star { opacity: 1; }

08サイズバリエーション

どう動くか

1つのSCSSミックスインがトラック幅・高さ・ノブの移動距離をまとめて計算し、小・中・大のスイッチを同じtranslateXアニメーションのまま作り分けます。オーバーシュート・squash・影の深さの変化も同じ比率でスケールするので、3サイズどれを押しても同じ手触りです。

どこで使うか

デザインシステムの部品のように、サイズごとに数値を再計算したくないときに向いています。

サイズバリエーション
新しいタブで開く

08 · サイズバリエーション — 計測 累積面積 2.444% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

@mixinem 단위0.25초
@mixin switch-size($track-w, $track-h, $knob) {
  --travel: #{$track-w - $knob - 6px};
  .switch__track { width: $track-w; height: $track-h; }
  .switch__knob  { width: $knob; height: $knob; }
}
.switch--sm { @include switch-size(36px, 20px, 14px); }
.switch--md { @include switch-size(48px, 26px, 20px); }
.switch--lg { @include switch-size(60px, 32px, 26px); }

09無効状態のスタイル

どう動くか

disabled属性が付くとトラックとノブがfilter: grayscaleとopacityのトランジションで薄くなり、そのままの位置で固まって反応しなくなります。薄くなる直前まではオーバーシュートするスプリングと影の深さ変化が残っていて、固まる瞬間まで他の8つと同じ質感を保っています。

どこで使うか

権限のない設定項目のように、触れられないという事実そのものを伝えたい場所に向いています。

無効状態のスタイル
新しいタブで開く

09 · 無効状態のスタイル — 計測 累積面積 1.517% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

filter:grayscaleopacitycursor:not-allowed
.switch__input:disabled ~ .switch__track { filter: grayscale(1); opacity: .45; }
.switch__input:disabled ~ .switch__label { opacity: .55; }

@keyframes fade-loop {
  0%, 20%  { filter: grayscale(0); opacity: 1; }
  50%, 70% { filter: grayscale(1); opacity: .45; }
  100%     { filter: grayscale(0); opacity: 1; }
}

どこで崩れるか — 落とし穴

色ではなく透明度で切り替える

9つのうち半分以上(01・02・04・05・08)は、background-colorそのものをアニメーションさせず、2枚の背景レイヤーを重ねてopacityだけで入れ替えています。色そのものを動かすと途中の値がブラウザごとに違う経路で補間され、意図しない濁った色を一瞬通過しますが、重ねたレイヤーを透明度だけで切り替えれば各レイヤーの色はにじみません。MDNのCSS・JSアニメーション性能の解説でも、レイアウトの再計算が要らないtransformとopacityを優先するよう勧めています。

2つの例外

06のclip-pathと07のbackground-positionはこの原則の例外に見えますが、どちらもリペイントだけで完結しレイアウトの再計算を要求しない点は同じです。caniuse.comでclip-pathを調べると、主要ブラウザはすでに前提として対応しています。

トラック幅と文字

落とし穴とまでは言えませんが、もう一つ気づいた点があります。03のON・OFFの文字は、ノブの直径よりトラックの幅が十分広くないと、文字が半分だけ隠れる中途半端な瞬間が生まれてしまいます。トラックとノブの色を設定画面全体とそろえるなら、ダークモード デザインシステムが公開しているトークンの表から拾うほうが早く決まります。ここに載せたトラック幅と移動距離をそのまま収めた完成版ファイルはa2ejkfw2という文字列を入れないと開けず、下のカードから取り出せます。

アクセシビリティ

キーボードで操作できるか

9つとも<input type="checkbox">にrole="switch"を載せた本物のコントロールなので、Tabで移動できSpaceで値を切り替えられます。:focus-visibleにはリングを描き、changeイベントのたびにaria-checkedを明示的にそろえて、古いスクリーンリーダーでも状態を取りこぼさないようにしています。

<label class="switch">
  <input type="checkbox" class="switch__input" role="switch" aria-checked="false">
  <span class="switch__track"><span class="switch__knob"></span></span>
</label>
.switch__input:focus-visible ~ .switch__track {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}
document.querySelectorAll(".switch").forEach(function (s) {
  var input = s.querySelector(".switch__input");
  var sync = function () { input.setAttribute("aria-checked", String(input.checked)); };
  input.addEventListener("pointerdown", function () { s.classList.remove("is-demo"); });
  input.addEventListener("change", sync);
  sync();
});

動きを減らす設定では

prefers-reduced-motion: reduceの画面では、9つとも切り替えのトランジションだけが消え、オン・オフの状態そのものはそのまま見えます。09だけはそもそも触れない状態なので、この設定の有無にかかわらず常に薄い見た目のままです。対応状況はMDNのprefers-reduced-motionの解説で確認できます。

チェックボックス型のコントロールをもっと見たい方はCSSアニメーション一覧を、godrichstoryがどんなサイトかは紹介ページを先に読んでみてください。

FAQ

トグルスイッチ デザイン css とチェックボックスは何が違いますか?

マークアップはどちらも同じ<input type="checkbox">ですが、role="switch"を載せるとスクリーンリーダーが「チェックボックス」ではなく「スイッチ」として読み上げます。通知のオンオフのようにすぐ反映される設定にはスイッチ、保存ボタンを押して初めて反映される項目にはチェックボックスの方が信号として正確です。

09はdisabledなのに、なぜ動いて見えるのですか?

実際のコントロールは常に無効な状態のまま固定されていて、画面で繰り返し再生されている薄れる動きはdisabledが付いた瞬間の見た目を伝えるためのデモ専用ループです。人がクリックしてオン・オフを切り替える操作は、この状態ではそもそも成立しません。

Reactではクラスの付け外しの代わりに何を使いますか?

zip内のreact/フォルダの各コンポーネントはcheckedをuseStateで持ち、色や速さを決める変数はpropsで受け取ってCSSカスタムプロパティに渡します。SCSSモジュール側はvar()を読むだけなので、値をいくつか変えるだけで違う色・速さのスイッチをすぐに作れます。スイッチの隣に置くボタンまで手触りをそろえるなら、受賞級の css ボタン ホバー アニメーションから05の押し込みと同じcubic-bezierを選ぶとそろいます。

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

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