【実務・中級編】 not否定擬似クラスの高度な組み合わせ – CSS実践ガイド

やあ、最近のコードの書きぶりはどうだい?
コンポーネント指向が当たり前になって、CSS-in-JSやらTailwind CSSやらが全盛の昨今だけど、結局のところ、バニラなCSSのセレクタの機微をどこまで理解しているかで、マークアップの美しさとメンテナンス性は大きく変わる。

今回は、中級からもう一段上のシニアへとステップアップしたい君に向けて、CSSの` `:not()` 否定擬似クラスの高度な組み合わせ方` について話をしよう。

「`not`くらい知ってるよ、特定のクラスを除外するんでしょ?」と思ったそこの君。
実はこの` :not()`、仕様の進化によってとんでもないパワーを手に入れているし、一歩間違えるとブラウザのレンダリングパフォーマンスをじわじわと蝕む「諸刃の剣」にもなるんだ。
現場のリアルな知見を交えて、徹底的に解剖してやろう。

—

1. モダンCSSにおける `:not()` の真価:カンマ区切りと詳細度の罠

まず大前提として、CSSの仕様(Selectors Level 4)からおさらいしておこう。
昔の` :not()` は、括弧の中に指定できるのは「単一のセレクタのみ」という厳しい制約があった。だから複数のクラスを除外したい時は、 `:not(.a):not(.b)` のように数珠繋ぎにするしかなかったんだ。見た目も悪いし、詳細度(Specificity)の計算もややこしくなる。

しかし、現代のモダンブラウザでは、`:not()` の中にカンマ区切りで複数のセレクタ(リスト)を渡せるようになっている。

/ 複数のクラスをまとめて否定する(モダンな書き方) /
.card:not(.is-disabled, .is-archived) {
border-color: var(–color-primary);
cursor: pointer;
}

この書き方、非常にスマートだよね。しかし、ここで実務上絶対に知っておかなければならない「詳細度の罠」がある。

ブラウザは裏側でどう処理しているか?

CSSのセレクタ詳細度は、「最も詳細度の高いセレクタ」が適用されるルールになっている。
では、`:not(.a, .b)` の詳細度はどう計算されるだろうか?

実は、`:not()` 自体の詳細度は 0 なのだが、括弧の中に含まれるセレクタの中で「最も強い詳細度」がそのまま `:not()` 全体の詳細度になる。
つまり、` :not(.a, #id-b)` のようにIDセレクタが混ざっていれば、その否定擬似クラス全体の詳細度はIDセレクタのそれに跳ね上がるんだ。

さらに重要なのは、「リストのどれか一つでもマッチすれば全体が除外される」という否定の論理だ。
ブラウザのCSSエンジン(BlinkやGeckoなど)は、要素を走査する際、この否定リストを評価するためにマッチングのコストを支払う。複雑なセレクタを `:not()` の中に突っ込みすぎると、レイアウト計算のフェーズで無駄なCPUサイクルを回すことになる。DOMツリーが数千個ある大規模なアプリケーションでは、これが原因でスクロールのパフォーマンス低下を招くこともあるんだよ。

—

2. 否定の連鎖(`:not(:first-child)` など)の極意

次に、実務でよく遭遇する「否定の連鎖」、例えば `:not(:first-child)` や `:not(:last-child)` について深掘りしよう。

リストのレイアウトなどで、「最初以外の要素にだけマージン(余白)を持たせたい」という要件は、フロントエンド開発の100本ノックレベルで頻出するよね。

昔はこれ、隣接セレクタを使って ` + ` と書くのが定番だった。

/ すべての「直前に要素がある」アイテムに上マージンを付与(伝統的な手法) /
.list-item + .list-item {
margin-top: 1.5rem;
}

もちろん、これでも動く。動くけれど、マークアップの構造が変わったり、間にコメントや別のラッパー要素が入ったりすると、途端に崩れる fragility(脆弱性)を抱えている。
ここで `:not(:first-child)` の出番だ。

/ 最初の子要素以外に上マージンを付与する直感的なアプローチ /
.list-item:not(:first-child) {
margin-top: 1.5rem;
}

これなら構造に強く、意図がコードから一目瞭然だ。しかし、ここにも実務ならではの落とし穴がある。

「予期せぬ要素」が混ざったときの挙動

もし、`.list-item` の親要素の中に、別種のタグや、CMSから出力された予期せぬ空の `div` が混ざっていたらどうなるだろう?

Item 1

Ad

Item 2

この場合、`.list-item:not(:first-child)` を適用すると、`.container` の中で「最初の子要素」ではないすべての `.list-item` が対象になる。
HTML構造上、`.ad-banner` が先頭にいるため、なんと「Item 1」に対してもマージンがついてしまうんだ。

「えっ、Item 1 は先頭なのに?」と思うかもしれない。
そう、`:first-child` は「`.list-item` の中で最初」という意味ではなく、「親要素から見て最初の子要素(それが何であれ)」を指すからだ。ここを混同してバグを生むジュニアエンジニアを、僕は数え切れないほど見てきた。

—

3. 現場ですぐに使える!実践的コードパターン

この「子要素の型の混同」を防ぎつつ、`:not()` をエレガントに使いこなすための実務パターンを授けよう。
構造化されたリストやカードグリッドでそのままコピペして使えるように組んでみた。

/ ————————————————————————–

  • 実務で使える堅牢なリストの余白管理
  • ————————————————————————– /

.product-list {
display: flex;
flex-direction: column;
}

/

  • 【解説】
  • 単純な :not(:first-child) ではなく、親の文脈やセレクタの組み合わせを意識する。
  • もしリスト内に異なるDOMが混ざる可能性があるなら、
  • :not() の中を厳密に絞るか、あるいは現代CSSなら `:is()` と組み合わせるのが最強。

/

/ パターンA: プレーンなリストで「最初の要素以外」にボーダーを引く /
.product-item {
padding: 1rem;
}

.product-item:not(:first-child) {
border-top: 1px solid var(–color-border-subtle, #e2e8f0);
}

/ パターンB: 応用編 – アクティブ状態やホバー状態以外の要素を一括制御する /
/

  • 複数のインタラクティブなカードがあり、
  • 「ホバーされていない、かつ無効化もされていないカード」のスタイルを落とし込む

/
.card-grid .card:not(:hover, .is-disabled) {
opacity: 0.6;
filter: grayscale(40%);
transition: opacity 0.3s ease, filter 0.3s ease;
}

このパターンBのコードを見てほしい。`.card:not(:hover, .is-disabled)` だ。
「ユーザーがマウスを乗せておらず、かつ、無効化(disabled)されていないカード」という複雑な条件を、わずか1行のセレクタで完璧に表現できている。これがモダンCSSの美しさであり、強力さだ。JavaScriptでわざわざクラスを付け外ししなくても、CSSだけで完結してしまうケースがこれだけ増えているんだ。

—

4. シニアからのアドバイス:保守性とパフォーマンスのバランス

最後に、現場で設計を行うチーフアーキテクトとしての心構えを伝授しよう。

`:not()` は非常に強力な反面、「何を選択しているか」ではなく「何を選択していないか」で思考するアプローチになるため、多用しすぎるとコードの可読性が落ちる。「この要素には一体どんなスタイルが当たっているんだっけ?」とデバッグ画面(DevTools)を何往復もする羽目になりかねない。

原則として、以下を意識してコードを組んでほしい。

1. 基本は「肯定(ポジティブ・セレクタ)」で書く:
まずは通常のクラスや属性セレクタで基本スタイルを定義し、例外的なケース(最初以外の要素、特定の状態以外など)を打ち消すために `:not()` をスパイス的に使う。
2. 否定の連鎖は避ける:
`:not(.a):not(.b)` のような多重否定は、人間の脳の処理負荷を高めるだけでなく、ブラウザのセレクタマッチング性能にも悪影響を与える。必ずカンマ区切りの `:not(.a, .b)` を使うこと。
3. 詳細度の爆発に気を付ける:
IDセレクタやカスタム属性を `:not()` の中に含めると、意図せず詳細度が跳ね上がり、のちのスタイルの上書き(Override)地獄につながる。

CSSは、ただ動けばいいというものではない。チームの誰もが意図を瞬時に理解でき、ブラウザにとっても負荷の少ない、キレのあるコードを書くことこそがプロの仕事だ。

今回の `:not()` の知見を、今日のコーディングから早速取り入れてみてくれ。君のコードベースがさらに洗練されることを期待しているよ。

コメント

タイトルとURLをコピーしました