おい、最近書いたフォーム周りのCSS、また「JSでクラスの付け外し」をしてやしないか?
「チェックが入ったらここのデザインを変えたいです」という仕様を見るたびに、素早く`element.classList.toggle(‘is-checked’)`なんて書き始めるジュニアが後を絶たない。ちょっと待て。俺たちが日々向き合っているCSSには、そんな無駄なJavaScriptのボイラープレートを一切排除し、ブラウザのネイティブな状態管理に完全にタダ乗りできる強力な武器が標準装備されている。
それが、今回深掘りする `:checked` 疑似クラス だ。
今回は、ただの「チェックボックスの色が変わる便利機能」というレベルを超えて、実務の現場で泥臭く、かつエレガントに使い倒すための極意を伝授しよう。ブラウザの裏側の挙動から、JSを1行も書かずにアコーディオンやタブUIを爆誕させる実用テクニックまで、しっかりと頭に叩き込んでいってくれ。
—
1. `:checked` 疑似クラスの基本と、ブラウザの裏側の話
まずは基本のおさらいだ。`:checked` は、``、``、そして `
ここで、一つシニアとしてお前らに問いたい。
「ブラウザは、どうやってこの `:checked` のON/OFFを判定していると思う?」
答えは、HTMLのDOMツリーが持つ `checked` プロパティ(内部状態) だ。
初期HTMLに `checked` 属性(attribute)が書かれているかどうかだけでなく、ユーザーが画面上でクリックした瞬間に、ブラウザはDOMノードの `checked` プロパティ(property)の真偽値を書き換えている。`:checked` 疑似クラスは、この「プロパティが `true` になっている瞬間」をCSSのセレクタエンジンがキャッチしてスタイリングを適用する仕組みになっている。
つまり、CSSだけでユーザーのインタラクションによる「状態」を完全に追跡できる。余計なイベントリスナーを貼る必要なんて、最初からないんだよ。
—
2. 現場で即戦力になる!「脱JS」UIパターン
じゃあ、ここからが本番だ。実務でよくある「チェックボックスやラジオボタンをトリガーにしたUI制御」を、CSSだけでどう美しく料理するか見せてやろう。
パターンA:カスタムチェックボックス(アクセシビリティを殺さない実装)
デザイナーは口を揃えて「デフォルトのチェックボックスはダサいから、ブランドカラーに合わせたイケてるデザインにして」と言ってくる。ここでよくある悪手が、本物の `` を `display: none;` で消し去り、適当な `div` にクリックイベントをバインドする地獄のような実装だ。
待て。それじゃあキーボード操作(Tabキーでのフォーカスやスペースキーでのチェック)が完全に死に、アクセシビリティ(a11y)の観点からアクセシビリティ監査で一発レッドカードを食らう。
正解は、「見た目のパーツは隠さず隠蔽し、隣接兄弟セレクタ(`+`)や間接兄弟セレクタ(`~`)でスタイリングする」だ。
/ CSS /
.checkbox-wrapper {
display: inline-flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
user-select: none;
}
/ ネイティブのチェックボックスはスクリーンリーダーのために残しつつ、視界から消す /
.checkbox-native {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/ カスタムのチェックボックス(見た目) /
.checkbox-ui {
position: relative;
width: 20px;
height: 20px;
background-color: #ffffff;
border: 2px solid #cbd5e1;
border-radius: 4px;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
/ ★ここに注目:inputが :checked になったとき、隣接する .checkbox-ui を変化させる /
.checkbox-native:checked + .checkbox-ui {
background-color: #2563eb;
border-color: #2563eb;
}
/ チェックマークの描画(擬似要素を使用) /
.checkbox-ui::after {
content: “”;
position: absolute;
top: 3px;
left: 6px;
width: 5px;
height: 10px;
border: solid #ffffff;
border-width: 0 2px 2px 0;
transform: rotate(45deg) scale(0); / 初期状態は非表示(スケール0) /
transition: transform 0.15s ease-in-out;
}
/ チェックが入ったらチェックマークをズームインさせる /
.checkbox-native:checked + .checkbox-ui::after {
transform: rotate(45deg) scale(1);
}
/ オマケ:キーボードフォーカス時のアクセシビリティ担保 /
.checkbox-native:focus-visible + .checkbox-ui {
outline: 2px solid #93c5fd;
outline-offset: 2px;
}
このコードの美しいところは、JSを1行も書かずに、キーボード操作のフォーカス(`:focus-visible`)とチェック状態(`:checked`)を完全に両立させている点だ。これがプロの仕事というやつだ。
—
パターンB:アコーディオン(トグルパネル)
「アコーディオンを開閉するのに、jQueryやVue/Reactのステート管理が必要だと思っていませんか?」——昔の俺ならそう言っていたかもしれない。しかし、現代のCSSであれば、ラジオボタンやチェックボックスの `:checked` を使えば、コンポーネントのステートすらCSS側に持たせることができる。
/ CSS /
.accordion-item {
border: 1px solid #e2e8f0;
border-radius: 6px;
overflow: hidden;
margin-bottom: 1rem;
}
/ ネイティブinputは非表示に /
.accordion-input {
display: none;
}
.accordion-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
background-color: #f8fafc;
cursor: pointer;
font-weight: 600;
transition: background-color 0.2s;
}
.accordion-header:hover {
background-color: #f1f5f9;
}
/ 開閉アイコンの矢印 /
.accordion-icon {
width: 10px;
height: 10px;
border-right: 2px solid #64748b;
border-bottom: 2px solid #64748b;
transform: rotate(45deg);
transition: transform 0.3s ease;
}
/ アコーディオンの中身(高さアニメーションのトリック) /
.accordion-content {
display: grid;
grid-template-rows: 0fr; / グリッドを使った滑らかな高さアニメーション /
transition: grid-template-rows 0.3s ease-in-out;
background-color: #ffffff;
}
.accordion-content-inner {
overflow: hidden;
padding: 0 1.25rem;
color: #475569;
line-height: 1.6;
}
/ ★ チェックが入った時の状態変化 /
.accordion-input:checked ~ .accordion-header .accordion-icon {
transform: rotate(-135deg); / 矢印を上に向ける /
}
.accordion-input:checked ~ .accordion-content {
grid-template-rows: 1fr; / 0frから1frに広げることで、中身の高さに応じた滑らかな開閉を実現 /
}
.accordion-input:checked ~ .accordion-content .accordion-content-inner {
padding: 1rem 1.25rem; / 開いたときだけパディングを付与 /
}
ここで使っている `grid-template-rows: 0fr` から `1fr` へのトランジションは、CSSアニメーション職人の間では定番のテクニックだ。従来の `max-height` を使うハックと違って、コンテンツの高さがどれだけ変動しようとも、ガタつくことなく滑らかに開閉する。これが `:checked` と組み合わさることで、極めて軽量なアコーディオンが完成する。
—
3. 実務でハマりがちな「落とし穴」とパフォーマンスの注意点
ここまで読めば、「じゃあ何でもかんでも `:checked` でCSSステート管理してしまえ!」と思うかもしれないが、シニアとして最後に重要な注意点を釘刺しておく。
1. フォームの初期値(デフォルト選択)とCSSの同期
サーバーサイドレンダリング(SSR)や静的サイトジェネレーターで最初から `checked` 属性が入っている要素と、クライアント側でJSが介入する要素が混ざると、CSSの初期状態がズレることがある。HTMLの構造(DOM)がCSSのセレクタ起点になるため、マークダウンの順番(兄弟関係)には細心の注意を払え。
2. スコープの肥大化とセレクタの複雑化
「あれ、どのインプットの `:checked` がこの要素を動かしているんだっけ?」と迷子になるような複雑なツリー構造を作ると、保守性が一気に地に落ちる。DOMの構造に依存しすぎるのも諸刃の剣だ。コンポーネントの設計は、せいぜい数階層の兄弟関係(`~` や `+`)に留めておくのが、チーム開発における精神衛生上も安全だ。
—
まとめ
`:checked` 疑似クラスは、単なる「フォームの見た目を変えるための機能」じゃない。
「DOMの内部状態をCSSのトリガーに変換する、最も純粋で強力なリアクティブ・インターフェース」だ。
JavaScriptのバンドルサイズを削り、ブラウザの描画エンジンに直接仕事をさせる。こういう細部の積み重ねが、Webサイトのパフォーマンスと、エンジニアとしての格の違いを生むんだ。
さあ、お前のプロジェクトにある無駄な `is-active` トグル用のJSコードを探し出して、このエレガントなCSSに置き換えてこい。結果を楽しみにしているぞ。

コメント