やあ。今日も今日とてCSSの海を泳ぎ回っているかい?
デザインカンプを忠実に再現するだけなら初級者でもできる。だが、保守性が高く、将来の仕様変更にもビクともしない堅牢なスタイルシートを組み上げるとなると話は別だ。特に「例外処理」の書き方一つで、そのコードがプロの仕事か、単なる応急処置の寄せ集めかが一発でバレる。
今回は、実務でCSSを書くなら避けて通れない、いや、むしろ積極的に使いこなしてほしい`:not()`疑似クラス(否定疑似クラス)について深掘りしよう。
「ああ、指定したセレクタ以外の要素を選ぶやつね、知ってるよ」と思ったそこの君。
少し待ってほしい。君が普段使っている`:not()`は、本当にそのポテンシャルを最大限に引き出せているかい?実は仕様の進化やブラウザの裏側の仕組みを知ることで、`:not()`はただの「除外フィルター」から、CSSアーキテクチャの強力な武器へと化けるんだ。
現場のリアルな知見を交えて、徹底的に解説していこうか。
—
1. `:not()`疑似クラスの標準仕様と、ブラウザが裏側でやっていること
まずは基本のおさらいと、一歩進んだ仕様の話だ。
`:not()`は、引数に渡した「セレクタに一致しない」要素をターゲットにする。
/ 「.is-active」が付いていないボタンをすべて選択 /
button:not(.is-active) {
opacity: 0.6;
}
これ自体は基本中の基本だ。だが、ここからがプロの視点だ。仕様の変遷において、`:not()`は非常に大きなアップデートを遂げていることを知っているかい?
近代CSSにおける「複セレクタ」と「詳細度」の罠
昔のCSS(Level 3の時代)の`:not()`には、大きな足かせがあった。それは「引数に取れるのは単一のセレクタ(Simple Selector)のみ」というルールだ。つまり、`:not(.class1, .class2)`のようなカンマ区切りでの複数指定や、複合セレクタが直感的に書けなかったり、ブラウザによって挙動が怪しかったりした。
しかし、Selectors Level 4以降、近代ブラウザ(Safariも含めて完全に普及済みだ)では、`:not()`のなかに複数のセレクタをカンマ区切りで渡せる(リスト化できる)ようになった。
/ モダンな書き方:複数の条件をまとめて除外する /
:not(.is-active, .is-disabled, [hidden]) {
cursor: pointer;
}
そして、ここで一番重要な詳細度(Specificity)の話をしよう。
ブラウザの裏側のエンジン(BlinkやGeckoなど)がCSSを解析する際、`:not()`自体の詳細度は0だが、括弧の中に書いたセレクタの中で「最も強い詳細度」がそのまま全体の詳細度になるという仕様がある。
/ この場合、IDセレクタ(#submit)が含まれているため、全体の詳細度が跳ね上がる /
div:not(#submit) {
background: #f0f0f0;
}
「あれ、なんでこのスタイルの適用が上書きされちゃんだ?」と悩んだときは、大抵この`:not()`の中身が原因だったりする。裏側の仕組みを理解していないと、無駄に詳細度を爆発させてしまい、後々のメンテナンスで痛い目を見る典型的なパターンだ。心当たりがないかい?
—
2. 現場で即戦力になる!実践的なユースケースと綺麗めコード
理屈はこのあたりにして、実際のフロントエンド開発でどう使うべきか、具体的なシチュエーションを見ていこう。コピペしてすぐにでもプロジェクトに組み込めるコードを用意した。
ユースケースA:リストの「最後の要素以外」にボーダーを引く(いわゆる “Lobotomized Owl” の進化系)
リストの区切り線(border-bottom)を入れるとき、最後の要素の下線だけは消したい。よくある要件だよね。
昔は `:last-child` で打ち消すのが定石だったが、`:not()`を使えばもっとスマートに書ける。
- ニュース記事 01
- ニュース記事 02
- ニュース記事 03(ここにはボーダーを出したくない)
.item-list li {
padding: 1rem 0;
/ 最後の要素「以外」に下線を引きまくる /
border-bottom: 1px solid var(–color-border, #eee);
}
/ 否定疑似クラスで最後の要素を除外する美しさ /
.item-list li:not(:last-child) {
/ ここに余計な打ち消し(border: none等)を書く必要が一切ない /
}
……いや、待て待て。これだと「除外するだけ」になってしまうから、正確にはこうだ。
/ 最初から「最後の要素以外」をターゲットにしてボーダーを付与する /
.item-list li:not(:last-child) {
border-bottom: 1px solid #e0e0e0;
}
これの何が素晴らしいかって、「打ち消しのコード(override)」を一切書かなくていいという点だ。CSS設計において、一度書いたスタイルを別のセレクタで打ち消す行為は、コードの複雑性を増す悪手になりやすい。`:not()`を使うことで、「最初から適用しない」というクリーンなロジックが組める。
ユースケースB:フォームのバリデーションにおける「入力中かつエラーではない」状態のスタイリング
複雑なフォーム画面を作るとき、ユーザーが入力している最中のインタラクティブなフィードバックはUXの命だ。ここで`:not()`と他の疑似クラスを組み合わせる真価が発揮される。
/
「プレースホルダーが表示されている状態(未入力)」でもなく、
「無効な状態(:invalid)」でもない、かつ、ユーザーが一度でも触った(またはフォーカスが当たっている等)の要素
……をスマートにスタイリングする。
/
.signup-form input:not(:placeholder-shown):not(:invalid) {
border-color: var(–color-success, #2e7d32);
background-image: url(“data:image/svg+xml,…”); / 成功アイコンなど /
}
.signup-form input:not(:placeholder-shown):invalid {
border-color: var(–color-error, #c62828);
}
`:placeholder-shown` と `:not()` をチェイン(連続して記述)させることで、「ユーザーが何かしらの文字を入力した瞬間」をエレガントに捉えることができる。JavaScriptでわざわざ `input` イベントを監視してクラスを付け外しする? いやいや、モダンなCSSならこれだけで完結するケースがたくさんあるんだ。
—
3. シニアが教える、`:not()`を使う際のアンチパターンと注意点
ここまで`:not()`の素晴らしさを語ってきたが、諸刃の剣でもある。間違った使い方をすると、コードが読めないブラックボックスになってしまう。チーム開発で嫌われるアンチパターンを共有しておこう。
アンチパターン1:深すぎるネストと可読性の崩壊
/ 読むだけで頭痛がしてくる悪い例 /
section:not(.is-archived) > div:not(.sidebar) ul li:not(.is-hidden) a:not([target=”_blank”]) {
color: blue;
}
おいおい、冗談はやめてくれと言いたくなるコードだ。
`:not()`の中にさらに別の条件を何重にも重ねたり、長いセレクタチェーンの一部に組み込んだりすると、「一体どの要素が選ばれているのか」を脳内パースするだけで数分かかるゴミコードが完成する。
CSSは「誰が読んでも一瞬で意図が伝わる」ことが正義だ。もし条件が複雑になりすぎるなら、BEMなどの命名規則を頼るか、HTML側に適切なクラス(例: `.is-target` など)を付与することを検討してほしい。道具に頼りすぎてHTMLの構造を無視してはいけないよ。
アンチパターン2:パフォーマンスへの過度な心配(と、現実)
「`:not()`を多用すると描画パフォーマンスが落ちるのでは?」と心配する真面目な後輩がいる。
結論から言うと、現代のブラウザのセレクタマッチングエンジン(Selectors API)は極めて高度に最適化されているため、常識的な範囲(数千要素レベル未満)で`:not()`を使ったからといって、体感できるほどのパフォーマンス低下が起きることはまずない。
ただし、巨大なDOMツリーのルート(`body` や “ の直下など)で `:not()` のような全称セレクタと組み合わせた超広範囲な否定を行えば、当然ブラウザのペイント・レイアウトコストには悪影響を与える。常識的なスコープ(特定のコンポーネント内)で使う分には、パフォーマンスよりも「可読性の維持」を優先して問題ない。
—
まとめ:例外を美しく扱うために
CSSのコーディングは、往々にして「例外処理の連続」だ。
「基本はこうだけど、ここだけは除外したい」「この状態以外の時にスタイルをあてたい」――そんな現場の泥臭い要求に対して、`:not()`疑似クラスは最高にスマートな解答を用意してくれている。
無駄なJavaScriptのイベント監視を減らし、CSSの記述量を削ぎ落とし、スタイルの詳細度をコントロールする。
今日から君の書くコードの「例外処理」を、`:not()`を使ってワンランク上の洗練されたものにアップデートしてくれ。期待しているよ。

コメント