CSSの進化は早い。だが、その進化のスピード感とは裏腹に、現場のコードベースを見てみると、未だに「とりあえず動くから」という理由だけで書かれた脆弱なセレクタや、ブラウザのレンダリングパイプラインを無駄に叩きまくる非効率なスタイリングが横行している。
特に、コンポーネント指向のモダンな開発(React, Vue, Svelteなど)において、スタイルのスコープ漏れを防ぎ、例外的な条件をスマートにハンドリングするために不可欠なのが `:not()` 否定擬似クラスだ。
今回は、この `:not()` の真の挙動、すなわち「複数のセレクタをカンマで一網打尽にする方法」や「否定の連鎖(Chaining)」がブラウザのエンジン内部で何を引き起こしているのか、そして私たちがどうやって堅牢でパフォーマンスの高いWebアプリケーションを構築すべきかについて、限界まで深く掘り下げていこう。
—
1. `:not()` の進化と仕様の現在地:なぜカンマ区切りがゲームチェンジャーなのか
昔のCSS(Level 3の時代)を知るシニアエンジニアならトラウマになっているかもしれないが、かつての `:not()` は引数を1つしか受け取れなかった。つまり、「`.a` でも `.b` でも無いもの」をスタイリングしようと思ったら、以下のように無駄にセレクタを連鎖させる必要があった。
/ 旧時代の悪夢:詳細度も跳ね上がる /
.item:not(.a):not(.b) {
/ スタイル /
}
これがCSS Selectors Level 4でモダンブラウザに実装されて以来、私たちは複数のセレクタをカンマ区切りで渡せるようになった。
/ 現代のスマートな記法 /
.item:not(.a, .b) {
/ スタイル /
}
この構文、一見すると単なる糖衣構文(Syntactic Sugar)に見えるかもしれないが、実はブラウザのパース処理とメモリ効率の観点から非常に重要な意味を持っている。
詳細度(Specificity)の罠を見抜け
ここで一度、CSSの根本的なルールである「詳細度」について思い出し、そして `:not()` が持つ特殊な仕様を再確認しよう。
`:not()` 自体は詳細度を持たない。
しかし、括弧内に渡されたセレクタの詳細度はそのまま引き継がれる。
ここで問題になるのが、カンマ区切りの `:not()` の詳細度の計算方法だ。仕様書(Selectores Level 4)によると、カンマ区切りの `:not()` の詳細度は、「引数の中で最も詳細度が高いセレクタのもの」になる。
/ このセレクタの詳細度は [.foo の詳細度] (0, 1, 0) になる /
.card:not(.foo, #bar) {
/ #bar が含まれているため、このセレクタ全体がID並みの高詳細度になる! /
}
これ、初見で見落とすエンジニアが絶えない。`.foo` を除外するついでに `#bar` を混ぜた瞬間、そのセレクタ全体の詳細度が跳ね上がり、他の場所で上書き困難なスタイルバグを引き起こす。`:not()` の中にIDセレクタや複合セレクタを混ぜ込むときは、詳細度のインフレに細心の注意を払うべきだ。
—
2. 否定の連鎖(Chaining)とブラウザレンダリングエンジンへの負荷
次に、実務でやりがちな「否定の連鎖」についてだ。例えば、リスト構造の中で「最初の子要素でもなく、最後の子要素でもなく、さらに特定のモディファイアクラスを持っていない要素」をターゲットにしたいとする。
/ 否定の連鎖 /
li:not(:first-child):not(:last-child):not(.is-disabled) {
background-color: var(–color-surface-muted);
}
このコード、動くには動く。だが、ブラウザのレンダリングエンジン(Blink, WebKit, Gecko)の内部挙動というミクロな視点に立ったとき、何が起きているだろうか?
マッチングコストとDOMツリーの走査
CSSのセレクタマッチングは、基本的に右から左(Right-to-Left)へ評価される。しかし、擬似クラスの評価、特に否定の連鎖は、ブラウザがDOMノードを舐め回す際にコストの高い条件分岐を幾重にも発生させる。
1. 要素が `li` であるか判定。
2. `:not(.is-disabled)` に該当するか(クラスのハッシュルックアップ)。
3. `:not(:last-child)` に該当するか(DOMツリーの兄弟ノードのポインタ参照)。
4. `:not(:first-child)` に該当するか(同上)。
これらが数千個の要素を持つ巨大な仮想DOMや実DOMのリストに対して適用されたとき、メインスレッド(Main Thread)のスタイル計算フェーズ(Recalculate Style)で確実にボトルネックになる。特に、動的にDOMが頻繁に追加・削除されるSPA(Single Page Application)において、こうした複雑な否定セレクタは、レイアウトシフトやスクロール時のフレームレート低下(Jank)の隠れた原因となり得る。
回避策:状態の反転とユーティリティクラスの活用
もしパフォーマンスがクリティカルなダッシュボードやデータグリッドを構築しているなら、CSSの計算量に頼るべきではない。むしろ、コンポーネントの設計段階で「否定」ではなく「肯定(Positive State)」のクラスを付与するアーキテクチャを採用すべきだ。
// Reactなどのコンポーネント側で状態をあらかじめ確定させる
/ CSS側は極限までシンプルに保つ /
.item–middle-active {
background-color: var(–color-surface-muted);
}
「CSSを汚さずにJavaScript側でロジックを持つべきではない」という古臭い dogma(教義)にとらわれてはいけない。現代のフロントエンド・アーキテクチャにおいては、CPUコストの高いCSSセレクタの評価を避け、JSで決定論的なクラスを付与する方が、結果としてレンダリングパフォーマンスが向上するケースが多々ある。このトレードオフを理解できているかどうかが、中級者と上級者の分かれ道だ。
—
3. 実践:堅牢なコンポーネント設計のための `:not()` 活用パターン
とはいえ、全ての動的状態をJS側で制御するのはDRY原則に反するし、CSSだけでエレガントに解決できる領域があるのも事実だ。ここでは、実務で実際に使える、堅牢で安全な `:not()` の高度な組み合わせパターンをコードとして提示しよう。
パターンA:モダンなフォームバリデーションのスタイリング
ユーザーが入力中のフォームにおいて、「エラーでもなく、かつフォーカスも当たっていないデフォルト状態のインプット」に対して、微調整を加えるケースを考えてみる。
/
inputタグのうち、
1. .is-errorクラスを持たず
2. :focus-visible(キーボード操作やタップによる明確なフォーカス)でもなく
3. :disabled(無効化)でもない
ものに対してのみ、繊細なボーダーを適用する
/
.form-input:not(.is-error, :focus-visible, :disabled) {
border-color: var(–color-border-subtle);
transition: border-color 0.2s ease;
}
/ エラー時のスタイルは明確に分離 /
.form-input.is-error {
border-color: var(–color-error);
}
このアプローチの美しいところは、状態が排他的(Exclusive)であるべきフォーム要素において、スタイルの衝突(Specificity Wars)を完全に回避できる点にある。
パターンB:CSSグリッドにおける「最後の要素以外」の余白管理
フレックスボックスやグリッドレイアウトで、アイテム間に一律のギャップ(`gap`プロパティ)を置けないレガシーな環境や、特定の条件下でのみマージンを相殺させたい場合の定番テクニックだ。
/
グリッドコンテナ内のアイテムで、
最後の行や最後の列に属さないもの、あるいは特定の除外フラグを持たないものにのみ
右マージンを付与する(古いブラウザや特定のレイアウトハック用)
/
.grid-item:not(:nth-child(3n), .u-exclude-spacing) {
margin-right: var(–spacing-md);
}
ここで `3n` のような構造的擬似クラスと `.u-exclude-spacing` というユーティリティクラスを `:not()` の中で同時に使うことで、「構造上のルール」と「CMSなどから流し込まれるイレギュラーなコンテンツ」を綺麗に調停できる。
—
4. チーフアーキテクトからの提言:CSSの「引き算」を制する者
`:not()` 否定擬似クラスは、CSSという言語における数少ない「論理演算子」の一つだ。しかし、強力なツールであるゆえに、その裏で何が行われているのかを意識しなければ、コードベースはあっという間にスパゲッティ化し、ブラウザのレンダリングパイプラインを蝕んでいく。
1. 詳細度のインフレに怯えよ:`:not()` の中にIDセレクタや複雑な複合セレクタを混ぜ込むな。引数の中で最も強い詳細度が全体に適用される。
2. 否定の連鎖を疑え:`:not().not().not()` のような記述は、ブラウザのスタイル計算に負担をかけるだけでなく、人間の認知負荷も高める。時にはJSでの状態管理や、肯定的なクラス設計へのリファクタリングを検討せよ。
3. 「例外処理」のスコープを限定せよ:スタイルは「こうあるべき(肯定)」をベースに構築し、`:not()` はあくまで「例外的な除外」のトッピングとして最小限に留めること。
CSSは単なるデザインの流し込みツールではない。ブラウザという名の仮想マシン上で実行される、立派なプログラムだ。そのメモリ効率と実行速度に思いを馳せながら、美しく、かつ強靭なスタイルシートを書き上げてほしい。

コメント