【テクニカル・上級編】 where関数型疑似クラス – CSS実践ガイド

CSSの詳細度(Specificity)の呪縛に囚われた夜が、君にもあるだろう。

「なぜ、このユーティリティクラスが上書きされないんだ……?」
「CSS変数をコンポーネントのルートで定義したのに、どこかの深層セレクタに負けていやがる」

私たちは長年、この詳細度という名の不可視の魔物と戦ってきた。`!important` という禁忌に手を出してコードベースを腐らせたり、無駄にセレクタをネストさせて詳細度を釣り上げたり。しかし、現代CSSのアーキテクチャにおいて、その不毛なゲームに終止符を打つ切り札が存在する。それが `:where()` 関数型疑似クラスだ。

今回は、単なる「詳細度が0になる便利な関数」という表面的なおしゃべりはしない。ブラウザのスタイル計算エンジン、メモリ効率、そして大規模アプリケーションにおける堅牢なCSSアーキテクチャの文脈から、`:where()` の本質を骨の髄まで解剖していこう。

—

1. なぜ `:where()` はゲームチェンジャーなのか?

`:is()` 疑似クラスが登場したとき、私たちはセレクタのDRY化を手に入れた。

/ 従来の冗長な記述 /
article h1, article h2, article h3 { color: var(–text-primary); }

/ :is() によるDRY化 /
article :is(h1, h2, h3) { color: var(–text-primary); }

しかし、`:is()` には致命的な(あるいは仕様通りの)トラップがあった。それは、「引数の中で最も詳細度が高いセレクタの詳細度をそのまま継承する」という仕様だ。もし引数の中に `.my-class` が混ざっていれば、全体の詳細度が跳ね上がる。

ここで登場するのが `:where()` だ。機能的には `:is()` と全く同じ――複数のセレクタをグループ化し、コードの重複を排除する。だが、決定的な違いが一つある。

`:where()` の内部にあるセレクタは、詳細度が「常に 0」になるのだ。

この事実が意味するアーキテクチャ上の優位性を、君は正しく理解しているだろうか?

2. アーキテクチャの観点:詳細度管理のパラダイムシフト

大規模なWebアプリケーション(Design Systemや巨大なSaaSなど)において、CSSのアーキテクチャ(ITCSSやBEMなど)は、詳細度の管理コストを下げるためにデザインされている。しかし、コンポーネントが複雑化するにつれ、「外部から渡されたユーティリティクラスでスタイルを上書きしたいのに、コンポーネント側の基本スタイルが強すぎて負ける」という矛盾が必ず発生する。

ここで `:where()` をベースにした「リセットレイヤー」や「ベーススタイル」の構築が活きてくる。

/
ベーススタイルの定義
詳細度が常に0なので、後からどのような単一クラスででも容易に上書き可能になる
/
:where(ul, ol) {
padding-left: 0;
list-style: none;
}

:where(a) {
color: inherit;
text-decoration: none;
}

このアプローチの美しさは、「スタイルは適用したいが、詳細度の権力は一切持ちたくない」という、コンポーネント設計者のエゴを完璧に満たせる点にある。詳細度 0 のベーススタイル群を作っておけば、開発者は詳細度の計算に脳のCPUサイクリングを消費する必要がなくなるのだ。

3. 実践:堅牢なコンポーネント設計とフォールバックの妙技

では、実務の現場でどのように `:where()` を組み込むべきか。ボタンコンポーネントを例に、その実践的なコードを見てみよう。

/
【ベースコンポーネント】
詳細度を一切持たせないことで、拡張性の高いプリミティブを構築する
/
:where(.btn) {
–btn-bg: var(–color-gray-200);
–btn-color: var(–color-gray-900);

display: inline-flex;
align-items: center;
justify-content: padx;
padding: 0.5rem 1rem;
background-color: var(–btn-bg);
color: var(–btn-color);
border: 1px solid transparent;
border-radius: var(–radius-md);
font-family: inherit;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease;
}

/ 状態の管理も :where() で包むことで、外部からの拡張性を担保する /
:where(.btn):hover {
–btn-bg: var(–color-gray-300);
}

:where(.btn):disabled {
opacity: 0.6;
cursor: not-allowed;
}

/
【修飾子(Modifier)やユーティリティによる上書き】
:where()で作られたベースに対し、通常のクラスセレクタ(詳細度: 0-1-0)を当てるだけで
いとも簡単に、かつ安全にスタイルを拡張・上書きできる
/
.btn-primary {
–btn-bg: var(–color-primary-500);
–btn-color: #ffffff;
}

.btn-primary:hover {
–btn-bg: var(–color-primary-600);
}

この設計の優れているところは、CSS変数(Custom Properties)のスコープと組み合わせたときにある。詳細度の低いベーススタイルの中で変数を定義し、それを状態やモディファイアで書き換える。`:where()` によってベースのセレクタウェイトが完全に剥ぎ取られているため、`.btn-primary` のようなシンプルなクラスが、詳細度の計算に悩まされることなく確実に勝利する。

4. ブラウザのエンジン内部とパフォーマンスの現実

ギークなら気になるところだろう。「`:where()` を使うことで、ブラウザのスタイル計算(Style Recalculation)やメモリ効率にどのような影響があるのか?」という点だ。

Blink(Chrome)やGecko(Firefox)などのモダンなレイアウトエンジンにおいて、セレクタのマッチング処理は、右から左(ターゲット要素から祖先方向)へ評価される。
`:where()` や `:is()` の内部で複数のセレクタが列挙されている場合、ブラウザは内部的に効率的なルックアップテーブルやハッシュ構造を構築してマッチングを最適化する。

メモリ効率の観点から言えば、`:where()` を多用することによるメモリフットプリントの増加は、実用上無視できるレベルだ。むしろ、コード量が削減され、DOMツリーに対するCSSルールの適用における「無駄なセレクタの競合解決」が減るため、レンダリングパフォーマンスにおいてはプラスに働くケースが多い。

ただし、無秩序な乱用は禁物だ。
すべてを `:where()` で囲んでしまうと、今度は「どのルールが適用されているのか」をデベロッパーツールで追うのが困難になる。CSSの可読性(Developer Experience)とのトレードオフを常に意識し、「詳細度を持たせたくないベース部分」にピンポイントで適用するのがプロのアーキテクトの仕事だ。

5. レガシーブラウザの呪縛とフォールバック戦略

現代において、主要ブラウザはすでに `:where()` を完全にサポートしている(Can I use でも95%以上のグローバルシェアを誇る)。しかし、エンタープライズ向けのレガシーシステムや、厳格な社内環境をターゲットにする場合、まだ無視できないこともあるだろう。

PostCSSなどのビルドツールを使用している場合でも、`:where()` を自動的に詳細度を維持したフォールバック(従来の記述)に変換するのは構造上難しい(詳細度を0にするというマジックを、詳細度を持つ構文に逆変換することは情報の損失を伴うため)。

したがって、漸進的エンハンスメント(Progressive Enhancement)の思想に則り、基本的にはそのままモダンブラウザ向けに出力し、サポート外の環境では「単にデフォルトのスタイルが当たる(レイアウトは壊れないが、詳細度の制御が一部効かなくなる)」というフェイルセーフな設計にしておくのが現実的だ。

—

結び:CSSを書く悦びを取り戻すために

`:where()` は、単なるシンタックスの糖衣(シュガーシンタックス)ではない。それは、CSSという言語が抱長年抱えてきた「詳細度の呪い」に対する、W3Cからの鮮やかなアンサーだ。

詳細度の計算に怯え、`!important` の泥沼に足を取られていた時代は終わった。
`:where()` を正しく理解し、君のCSSアーキテクチャの基盤に組み込むことで、コードベースは驚くほど軽やかになり、拡張性に満ちた堅牢なシステムへと生まれ変わるだろう。さあ、エディタを開き、詳細度 0 の美しい世界へ飛び込もう。

コメント

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