.article h1, .article h2, .article h3,
.article h4, .article h5, .article h6 {
font-weight: 700;
line-height: 1.3;
}
CSS を書いていると、こういう 同じスタイルを違うセレクタに何度も書く 場面に必ず出会います。これを劇的に短く書けるのが、:is() と :where() です。
両者はほぼ同じ働きをするのですが、たった1つだけ決定的な違いがあります。それが「特異性(specificity)の扱い」です。
この記事では、:is() と :where() の基本から、両者の違い、迷わず使い分ける指針までを解説します。
この記事のゴール
:is() と :where() の 特異性の違い を理解し、ライブラリ作者と通常スタイルで 正しく使い分けられるようになることです。
:is() とは
:is() は、複数のセレクタを1つにまとめる擬似クラスです。
/* 従来の書き方 */
.article h1,
.article h2,
.article h3,
.article h4,
.article h5,
.article h6 {
font-weight: 700;
}
/* :is() を使った書き方 */
.article :is(h1, h2, h3, h4, h5, h6) {
font-weight: 700;
}
セレクタリスト(カンマ区切り)を :is(...) の中に入れるだけ。スタイルの内容はそのままで、書き方だけが劇的に短くなります。
より実用的な例
ナビゲーション内のリンクに、状態ごとに色を変えたいとき。
/* 従来: 状態の数だけセレクタを書く */
.nav a:hover,
.nav a:focus,
.nav a:active,
.nav a[aria-current="page"] {
color: #0ea5e9;
}
/* :is() で1行 */
.nav a:is(:hover, :focus, :active, [aria-current="page"]) {
color: #0ea5e9;
}
「複数の条件のうち、どれか1つでも当てはまる」というロジックを、自然な日本語に近い感覚で書けます。
:where() とは
:where() も書き方は完全に同じです。
.article :where(h1, h2, h3, h4, h5, h6) {
font-weight: 700;
}
ここまでは :is() と全く同じ。違うのは、後述する「特異性」だけです。
決定的な違い: 特異性(specificity)
:is() と :where() の唯一にして最大の違いはこれです。
| セレクタ | 特異性 |
|---|---|
:is(...) | 引数の中で最も特異性が高いセレクタ と同じ |
:where(...) | 常にゼロ(特異性を一切持たない) |
具体例で見る
<article class="article">
<h2 id="title">記事のタイトル</h2>
</article>
/* :is() を使う */
.article :is(h2, #title) { color: red; }
/* → 特異性は #title(ID)レベル。とても強い */
/* :where() を使う */
.article :where(h2, #title) { color: blue; }
/* → 特異性は 0。とても弱い */
この場合、後述する他のスタイルとの 優先度勝負 が決定的に変わります。
使い分けの指針
| 状況 | 使うべき |
|---|---|
| 普段のスタイル(自分のアプリ用) | :is() |
| リセットCSS や ライブラリ提供のデフォルト | :where() |
| 利用側が 後から簡単に上書きできるようにしたい | :where() |
| 確実にスタイルを 効かせたい | :is() |
実用パターン1: リセットCSSで :where() を使う
これがまさに :where() の本領発揮の場面です。
/* リセットCSS の典型例 */
:where(h1, h2, h3, h4, h5, h6) {
margin: 0;
font-weight: inherit;
}
:where(ul, ol) {
list-style: none;
padding: 0;
}
:where() の特異性は ゼロ なので、利用側で h1 { margin: 24px; } と書くだけで簡単に上書きできます。:is() だったら :is(h1) の特異性が h1 と同じなので、後勝ち / 先勝ちのルールに左右されてしまいます。
「他人に提供するスタイル」には常に :where() と覚えてしまって構いません。
実用パターン2: ネストした親セレクタを短く
階層が深い CSS でも、:is() で見やすく書けます。
/* 従来 */
.card .header h2,
.card .header h3,
.card .body h2,
.card .body h3 {
font-family: serif;
}
/* :is() で */
.card :is(.header, .body) :is(h2, h3) {
font-family: serif;
}
セレクタの 掛け合わせ(直積) を表現できるので、組み合わせ爆発を防げます。
実用パターン3: フォーム要素の一括スタイル
.form-control :is(input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="tel"], input[type="url"], textarea, select) {
border: 1px solid #d1d5db;
border-radius: 6px;
padding: 8px 12px;
}
.form-control :is(input, textarea, select):focus {
outline: 2px solid #0ea5e9;
outline-offset: 2px;
}
input[type="..."] を全部書き出すよりはるかに見通しが良く、漏れも起きにくくなります。
エラーが混ざっても無視される(寛容なセレクタ)
:is() と :where() は、引数の中に 未知のセレクタや誤ったセレクタが混ざっていても、その部分だけ無視してくれます。
/* :unknown-pseudo はブラウザが知らない擬似クラス */
:is(h1, :unknown-pseudo, h2) { color: red; }
/* → h1 と h2 にはちゃんと色がつく */
従来のセレクタリスト h1, :unknown-pseudo, h2 { ... } は、1つでも未知のセレクタが混ざるとリスト全体が無効化されてしまいました。:is() / :where() の寛容さは、新しいブラウザ機能をプログレッシブに使える という意味でも便利です。
ブラウザ対応
:is() と :where() は、2021年に主要ブラウザでサポートされ、現在は Baseline 安定 です。2026年では何の心配もなく利用できます。
つまずきやすいポイント
⚠ ここは要注意
:is() の中に #id や .class を混ぜると、特異性が想像以上に高くなり、上書きできない CSS の温床になります。
- 特異性の罠:
:is(h1, #title)は ID の特異性を採用するので、後からh1 { ... }で上書きできなくなります :not()との混同::not()も似ているが、こちらは「含まないものを選択」する。逆の意味:where()を多用しすぎない: なんでも:where()でゼロにすると、今度は どのスタイルが優先か分からなくなる。普段使いは:is()、リセット系だけ:where()
まとめ
:is() と :where() を使うと、以下が実現できます。
- ✅ 重複したセレクタを 1行にまとめられる
- ✅ 状態(
:hover/:focus等)の組み合わせをスッキリ書ける - ✅ リセットCSSで
:where()を使えば 利用側が簡単に上書きできる - ✅ 未知のセレクタが混ざっても 他のセレクタが死なない
「セレクタの組み合わせ爆発」が起きた瞬間に手が伸びる、CSS 書きの 必携ツール です。
💡 次回(最終回)は ネイティブ CSS Nesting を取り上げます。Sass / SCSS なしでセレクタを入れ子に書けるようになった、もう1つの大きな進化を解説します。