CSS 製トグルの元 checkbox は display: none ではなく opacity: 0 で隠し、:focus-visible でリングを描く
見た目を自作するために <input type="checkbox"> を display: none にするとフォーカスごと消えて Tab キーやスクリーンリーダーから操作できなくなるので、opacity: 0 で見えなくするだけにして、フォーカス時のリングは自作の見た目側に描く。
どんな時に使う?
- チェックボックスやラジオボタンを、トグルスイッチ・カード型の選択肢など独自の見た目に作り替える時
- 既存サイトで「トグルが Tab で選べない」「Space で切り替わらない」と指摘された時
- AI に生成させたトグルのコードをレビューする時(
display: noneで隠しているコードは多い)
コード
html
<label class="toggle">
<input type="checkbox" class="toggle__input">
<span class="toggle__track" aria-hidden="true"></span>
通知を受け取る
</label>css
.toggle {
position: relative;
display: inline-flex;
align-items: center;
gap: 0.5em;
}
/* 元の checkbox は見えなくするだけ。存在とフォーカスは残す */
.toggle__input {
position: absolute;
inset: 0; /* ラベル全体に重ねてクリックも受けられるように */
margin: 0;
opacity: 0;
cursor: pointer;
}
.toggle__track {
width: 2.5em;
height: 1.4em;
border-radius: 999px;
background: #ccc;
}
.toggle__input:checked + .toggle__track {
background: #1a7f37;
}
/* キーボード操作の時だけフォーカスリングを見た目側に描く */
.toggle__input:focus-visible + .toggle__track {
outline: 2px solid #1a7f37;
outline-offset: 2px;
}display: noneやvisibility: hiddenはフォーカス対象からもアクセシビリティツリーからも外れる。opacity: 0なら要素は残るので、Tab で選んで Space で切り替えられ、スクリーンリーダーにも「チェックボックス」として読まれる。- 元の input は見えないので、フォーカスリングは隣の見た目要素に
:focus-visible + .trackで描く。:focusではなく:focus-visibleにすると、マウスクリック時にはリングが出ない。
注意点・対応状況
opacity: 0の input がレイアウトの邪魔になる場合は、いわゆる「visually-hidden」パターン(clip-path: inset(50%)と 1px サイズなど)で隠してもよい。どちらも「フォーカスできる状態のまま見えなくする」点が要点。appearance: noneで checkbox 自体の見た目を消して、その input に直接スタイルを当てる方法もある(隠す必要が無くなる)。- 状態をスクリーンリーダーに「オン/オフ」と読ませたい場合は
role="switch"を付ける。 - 元の投稿はトグル生成ツールの紹介を兼ねた図解カード。上のコードは図解の「原因・直し方」から組み立て直したもの。