近年、CSSは劇的な進化を遂げており、これまでJavaScriptを使わなければ実現できなかった表現がCSSだけで可能になっています。その筆頭が :has() / :is() / :where() です。
この記事では、これらの「モダンセレクタ」の使い方と、実務でどう使い分けるかを分かりやすく解説します。
目次
コードをスッキリさせる :is() と :where() の使い方
まずは、複数のセレクタをまとめることができる「グループ化セレクタ」から紹介します。
:is():記述を短縮する
例えば、記事内の見出し(h1, h2, h3)にホバーした時のスタイルを書く場合、これまでは以下のように書いていました。
/* 従来の書き方 */
article h1:hover,
article h2:hover,
article h3:hover {
color: blue;
}
/* :is() を使った書き方 */
article :is(h1, h2, h3):hover {
color: blue;
}
実際の使い方を見てみましょう。
<div class="container">
<div class="content">
<h2>ここにh2見出しが入ります</h2>
<p>ここにテキストが入ります。</p>
<h3>ここにh3見出しが入ります</h3>
<p>ここにテキストが入ります。</p>
<h4>ここにh4見出しが入ります</h4>
<p>ここにテキストが入ります。</p>
</div>
</div>.content :is(h2, h3, h4, p) {
margin: 0;
}上記例では、複数要素のmarginをまとめて設定しています。
:where():詳細度を「0」にする
:is() と全く同じように使えますが、大きな違いが1つあります。それは 「詳細度(優先順位)が常に0になる」 という点です。
:is():カッコ内の最も高い詳細度が採用される:where():どれだけ長い指定をしても詳細度は 0
<div class="container">
<div class="content">
<h2>ここにh2見出しが入ります</h2>
<p>ここにテキストが入ります。</p>
<h3>ここにh3見出しが入ります</h3>
<p>ここにテキストが入ります。</p>
<h4>ここにh4見出しが入ります</h4>
<p>ここにテキストが入ります。</p>
</div>
</div>:where(.container .content p) {
color: red;
}
p {
color: blue;
}
:is(.container .content h2) {
color: red;
}
h2 {
color: blue;
}上記の例では、:where()の中で詳細な指定をしていますが、()の中で指定している箇所については詳細度が0(0,0,0)になるため、p{ color: blue; } に上書きされています。
一方、h2要素は:is()で指定しており()内の詳細度は(0,2,1)になるため、h2 { color: blue;} (0,0,1)よりも優先されています。
使い分けのヒント:
「あとから簡単に上書きさせたいベースのスタイル」を作る時は :where() を使い、「しっかり優先させたい」時は :is() を使うのが定石です。
待望の親セレクタ :has()
CSS界の長年の夢だった「親要素を操作する」機能が、ついに :has() で実現しました。
これは「特定の要素を持っている親」にスタイルを当てることができます。
以下の例では、img要素を持つ.cardのborderだけ赤色にしています。
例:画像があるカードだけスタイルを変える
<div class="card">
<img src="https://placehold.jp/150x150.png" alt="SAMPLE" class="image">
<p>ここにテキストが入ります。</p>
</div>
<div class="card">
<p>ここにテキストが入ります。</p>
</div>.card {
padding: 8px;
border: 1px solid #eee;
margin-bottom: 16px;
}
.card:has(img) {
border-color: red;
}これまでJavaScriptの classList.add() などで行っていた処理が、CSSだけで完結します。
クラス名の指定でも適用されます。
<div class="card">
<img src="https://placehold.jp/150x150.png" alt="SAMPLE" class="image">
<p>ここにテキストが入ります。</p>
</div>
<div class="card">
<p>ここにテキストが入ります。</p>
</div>.card {
padding: 8px;
border: 1px solid #eee;
margin-bottom: 16px;
}
.card:has(.image) {
border-color: red;
}例:チェックボックスにチェックが入ったら親の色を変える
次の例では、チェックボックスにチェックが入った時に親要素の背景色を変えます。
<div class="form-group">
<label><input type="checkbox" name="confirm">確認しました。</label>
</div>
<div class="form-group">
<label><input type="checkbox" name="confirm2">確認しました。</label>
</div>/* 「チェックされたinput」を持っている .form-group の背景を変える */
.form-group {
padding: 16px;
}
.form-group:has(input:checked) {
background-color: #f0f8ff;
}複雑な処理をしないでも、簡単にこのような表現を実装できます。
違いをひと目で比較
:is()と:where()、:has()の違いは以下の通りです。
| 特徴 | :is() | :where() | :has() |
| 主な用途 | セレクタの短縮 | セレクタの短縮 | 親・兄弟要素の選択 |
| 詳細度 | カッコ内の要素に準ずる | 常に 0 | カッコ内の要素に準ずる |
| 主なメリット | コードが読みやすくなる | 上書きが容易になる | JSなしで複雑な条件分岐が可能 |
組み合わせての活用例
これらを組み合わせることで、非常に強力な指定が可能になります。
/* 「セクション」または「アーティクル」の中にある
「h2」または「h3」が、
「aタグ(リンク)」を持っている場合
*/
:is(section, article) :is(h2, h3):has(a) {
text-decoration: underline;
}
ただし、あまり使いすぎると可読性が損なわれるので注意してください。
まとめ
:is()は、コードをスッキリさせたいときに使う。:where()は、詳細度を上げずに(リセットCSSなどで)まとめたいときに使う。:has()は、「〜を持っているなら」という条件でスタイルを変えたいときに使う。
これらのセレクタは、現在すべてのモダンブラウザでフルサポートされています。これからのWeb制作では必須の知識と言えるでしょう。