おい、最近CSSの詳細度(Specificity)の計算で無駄に頭を悩ませてないか?
「なんか知らんけど、このユーティリティクラスが上書きできねぇ……!」って夜中に頭を抱えて、仕方なく苦し紛れに `!important` をブッ挿した経験、中級への階段を登っている君なら一度や二度じゃないはずだ。その気持ち、痛いほどよく分かる。
CSSの歴史において、詳細度との戦いは常にエンジニアの頭痛の種だった。特に、複数の要素をまとめてスタイリングしたいときによく使う `:is()` 疑似クラス。こいつは便利なんだけど、引数の中で一番詳細度が高いやつの影響を引き継ぐという性格の悪さがあった。
そこで救世主として登場したのが、今回解説する `:where()` 関数型疑似クラス だ。
こいつを使いこなせるようになると、詳細度の呪縛から解放されて、驚くほどメンテナス性の高いCSSが書けるようになる。今日は、その実務での「ガチな使い方」を徹底的に叩き込んでやるから、しっかりついてきな。
—
1. `:where()` とは何か? ブラウザの裏側の動きを含めて理解する
まずは基本の復習からだ。`:where()` は、`:is()` と全く同じ構文を持つ。複数のセレクタをグループ化して、DRY(Don’t Repeat Yourself)なコードを書くための関数型疑似クラスだな。
例えば、記事内の見出しの色を一括で変えたいとき、従来ならこう書いていただろ?
article h1,
article h2,
article h3 {
color: #333;
}
これが `:where()` を使えば、こうスッキリ書ける。
:where(article) :where(h1, h2, h3) {
color: #333;
}
「お、スッキリしていいじゃん」と思ったそこの君。本質はそこじゃない。
`:where()` の最大の、そして唯一無二の特性は、「セレクタ全体の詳細度が常に `0` になる」 という点だ。
ブラウザは裏側でどう処理しているか?
ブラウザのCSSエンジンは、セレクタをパースするときに詳細度(IDセレクタ、クラスセレクタ、要素セレクタの数)を計算してツリー構造に重み付けをする。通常、要素セレクタが混ざればそれに応じたポイントが加算されるよな。
だが、`:where()` の内部に含まれるセレクタは、ブラウザの計算において「存在しないもの(詳細度 0)」として扱われる。
つまり、どれだけ複雑なセレクタを中に詰め込もうとも、`:where()` 自体と、その中にあるセレクタのスコアは一切加算されない。この「詳細度の無効化」という特権こそが、現場の現場で我々を救う武器になるんだ。
—
2. `:is()` との違い:なぜ実務では `:where()` が選ばれるのか?
ここで、よく比較される `:is()` との決定的な違いを確認しておこう。
- `:is()` の詳細度: 引数の中で最も詳細度が高いセレクタのものになる。
- `:where()` の詳細度: 常に `0`。
百聞は一見に如かずだ。コードでその違いを見てみよう。
/ 1. :is() の場合 /
:is(.card, #unique-card) h2 {
color: red;
}
この場合、#unique-card(IDセレクタ)が含まれているため、このセレクタ全体の詳細度は「ID 1つ分+要素セレクタ1つ分」と判定される。後から別のクラスで上書きしようとしても、詳細度が高すぎて跳ね返されてしまうわけだ。
では、これを `:where()` に変えるとどうなるか?
/ 2. :where() の場合 /
:where(.card, #unique-card) h2 {
color: red;
}
なんと、中に `#unique-card` が入っていようが、このセレクタの詳細度は `0-0-1`(h2要素の1点のみ) になる。
つまり、後から普通のクラスセレクタ(例:`.text-blue`)を当てるだけで、あっさりスタイルを上書きできるようになるんだ。これ、リセットCSSやデザインシステムの基盤を作る上で、どれだけ強力か分かるか?
—
3. 【実務Tips】コピペで使える! `:where()` の神ユースケース
ここからが本番だ。実務の現場で「おっ、こいつ分かってるな」と思わせる `:where()` の具体的な活用パターンを3つ紹介する。
ユースケース A: デザイナー泣かせの「詳細度バトル」を防ぐリセットCSS
デザインシステムやUIライブラリのベーススタイルを作るとき、ユーザーが後から独自のクラスで簡単にスタイルを上書きできるようにしたい。そんなときは `:where()` でベースを組むのが鉄則だ。
/ ベースのスタイルを定義。詳細度は「0」なので、後からの上書きが非常に容易。 /
:where(a) {
color: var(–color-primary);
text-decoration: underline;
}
:where(button) {
cursor: pointer;
border: none;
background: transparent;
}
/ 後から普通のクラスで余裕で上書きできる /
.custom-link {
color: #ff5722; / 無事に赤色になる! /
}
従来のCSSだと、リンク要素のスタイルを上書きするためにわざわざ詳細度を上げるクラスを作ったり、不毛な詳細度戦争が起きていた。`:where()` を使えば、その無駄なストレスが綺麗に消え去る。
ユースケース B: 「状態」のデフォルトスタイルを安全に定義する
ボタンやカードのホバー・フォーカス状態を管理するときにも `:where()` はぶっ刺さる。
/ どんなボタンであれ、ホバーやフォーカス時の共通ベースを作る /
:where(button, .btn) {
transition: background-color 0.2s ease, transform 0.1s ease;
}
:where(button, .btn):hover {
transform: translateY(-2px);
}
/ 特定の特別なボタンだけ、個別の詳細度を気にせずスタイルを追加できる /
.btn-danger:hover {
background-color: #d32f2f; / :where()のせいで負ける心配がない /
}
「状態」のフックを `:where()` でラップしておけば、個別のコンポーネント側で詳細度を気にしてセレクタをこねくり回す必要がなくなる。コードの治安が劇的に良くなるんだ。
ユースケース C: ユーティリティファーストな設計の裏方として
もし君のチームが Tailwind CSS のようなユーティリティファーストなフレームワーク、あるいはそれに近いカスタムCSS設計を採用しているなら、`:where()` は神のツールになる。
詳細度をフラットに保ちたいからといって、やたらとタグセレクタを排除する必要はない。`:where()` を使って「タグそのものにデフォルトスタイルを与えつつ、詳細度を上げない」というテクニックが使える。
/ リストのデフォルトスタイル。ul や ol の生タグにスタイルを当てても詳細度は 0 /
:where(ul, ol):where([class]) {
list-style: none;
padding: 0;
margin: 0;
}
この書き方、めちゃくちゃスマートだと思わないか? `[class]` 属性がついているリスト要素だけに絞りつつ、詳細度は一切持たせない。これにより、後からどんなユーティリティクラス(例:`.pl-4` など)をあてがっても、詳細度の競合で負けることがなくなる。
—
4. 注意点:使ってはいけないシチュエーション
ここまでベタ褒めしてきた `:where()` だが、もちろん銀の弾丸(万能薬)じゃない。シチュエーションを間違えるとバグの元になる。
- 「絶対に上書きされたくない重要すぎるスタイル」には使うな
セキュリティ上の警告表示や、レイアウトが崩壊しては困る絶対的なフォールバックなど、「何があってもこのスタイルを死守したい」という場合には `:where()` を使ってはいけない。詳細度が `0` になるということは、意図せず別の低いセレクタに負けるリスクも孕むということだからな。
—
まとめ:明日からのコーディングで使ってみろ
`:where()` は、CSSの詳細度という長年の呪縛を、モダンな仕様で綺麗にハックするための強力な武器だ。
1. セレクタをDRYにまとめられる
2. 詳細度が常に `0` になるため、後からの上書きが圧倒的にラクになる
3. リセットCSSやデザインシステムのベースレイヤーと相性抜群
理屈を理解したら、次は手を動かす番だ。明日のスプリントのタスクで、冗長になっているセレクタを見つけたら、すかさず `:where()` に書き換えてみろ。「お、なんか今回のコード、いつもより綺麗に書けるぞ……?」って実感できるはずだ。
フロントエンドのプロとして、コードの美しさと保守性は妥協するなよ。それじゃ、次の現場でもクールなコード頼むぜ!

コメント