できない.dev

CSSでトグルスイッチを作るには

チェックボックスを視覚的に隠したうえで、:checked と隣接兄弟セレクタを使ってオン・オフが切り替わるトグルスイッチの見た目を作る。
JavaScript は使わない。

実行確認済み

実行確認済みは、ブラウザで実際に描画して見た目を確認したものである。

公開:

プレビュー
<label class="switch">
  <input type="checkbox" class="switch__input" checked />
  <span class="switch__track"></span>
  <span class="switch__text">通知を受け取る</span>
</label>
 
<label class="switch">
  <input type="checkbox" class="switch__input" />
  <span class="switch__track"></span>
  <span class="switch__text">位置情報を使う</span>
</label>
.switch {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  color: #27272a;
  font-family: system-ui;
  font-size: 14px;
  /* label で包むと、文字をクリックしても切り替わる */
  cursor: pointer;
}
 
/* input は display: none にせず、視覚的にだけ隠す。
   none にするとフォーカスもキーボード操作も失われる */
.switch__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
 
.switch__track {
  position: relative;
  flex: none;
  width: 46px;
  height: 26px;
  border-radius: 999px;
  background: #a1a1aa;
  transition: background-color 0.2s ease;
}
 
/* つまみ。track の中を左右に動かす */
.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, 0.3);
  transition: transform 0.2s ease;
}
 
/* :checked を隣接兄弟セレクタで受け取り、見た目だけを差し替える */
.switch__input:checked + .switch__track {
  background: #10b981;
}
 
.switch__input:checked + .switch__track::after {
  transform: translateX(20px);
}
 
/* 隠した input のフォーカスリングは見えないので、track に出し直す */
.switch__input:focus-visible + .switch__track {
  outline: 2px solid #0f766e;
  outline-offset: 3px;
}

チェックボックスを視覚的に隠し、隣に置いた要素へ :checked の状態を伝えてスイッチの見た目を作る。
input を display: none で消さないのは、消すとフォーカスもキーボード操作も失われ、支援技術からチェックボックスとして扱われなくなるためである。

つまずき

切り替わらないときは、まず input と track が兄弟になっているかを確認するとよい。
隣接兄弟セレクタ + は「直後の兄弟」しか選べないため、input を div で包んだり、input と track の間に別の要素を挟んだりすると :checked の状態が届かなくなる。
構造を変えたい場合は、共通の親に :has(:checked) を書く方法もある。

input を消さずに隠す

display: none や visibility: hidden で消したチェックボックスは、タブキーで到達できず、スクリーンリーダーからも読まれない。
見た目だけを消して要素は生かすために、上の例では 1px の領域に押し込んで clip-path で切り取っている。
opacity: 0 のまま track の上に重ねる書き方でも同じ目的を果たせる。
いずれの場合も、操作しているのは本物のチェックボックスなので、フォームに入れればそのまま値が送信され、required や disabled もそのまま効く。

フォーカスの見え方

input を隠すとブラウザ既定のフォーカスリングも一緒に見えなくなるので、:focus-visible を使って track 側に出し直す。
:focus ではなく :focus-visible を選ぶのは、マウスでクリックしたときには枠を出さず、キーボードで移動したときだけ出すためである。
アニメーションは transition で track の背景色とつまみの transform に掛けている。
prefers-reduced-motion を尊重するなら、この 2 つの transition を無効にする指定を足すとよい。

この記事は役立ちましたか?