CSSは常に進化を続けていて、開発者にとってより便利な新しいセレクタも登場してきています。
その中でも特に便利なセレクタが、:has()、:is()、、:where()の3つです。
この3つの新しいセレクタは、「子要素を持つかどうかの判定」や「詳細度を上げない」といった待望の仕様を備えており、CSSの開発効率を向上させる強力なツールになるでしょう。
本記事では、それぞれのセレクタの使い方と具体的な使用例をデモサイトを交えて解説します。
それではどうぞ!
目次
:has()
今まで、特定の要素の存在のあり・なしをCSSで判別する方法は、「それぞれのクラスを作成して調整する方法」または、「JSで調整する方法」しかありませんでした。
しかし:has()セレクタは、指定したセレクタ内に特定の要素が存在する場合にCSSを適用できます。
子要素や隣接要素があるかどうか、またはその状態(ホバー・フォーカス等)に応じて、要素のスタイルを変えることが可能になったということです。これは待ち望んでいた人も多い機能ではないでしょうか。
■参考ドキュメント
:has() | MDN
使用例
主な使い方としては以下の3パターンが挙げられます。
特定の要素がセレクタ内にあるかどうか判別する
特定の要素がある要素の後に続いているかどうか判別する
form要素でフォーカスされたinput要素があるか判別する
具体的な書き方は以下の通りです。
1 2 3 4 5 | 親要素:has(子要素) { /* スタイルの宣言 */ } |
特定の要素がセレクタ内にあるかどうか判別する
一例として、カードレイアウトで画像がある場合・ない場合で条件分岐をしてCSSの適用する方法をご紹介します。
画像がある場合はテキストと横並びになるように調整します。
デモはこちらです。
:has – 特定の要素がセレクタ内にあるかどうか判別する
▼HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <div class="card"> <div class="card-img"> <img src="./img/card-img01.jpg" alt=""> </div> <div class="card-content"> <h3 class="card-title">Card Title</h3> <p class="card-description">This is a card description. This is a card description. This is a card description. This is a card description.</p> </div> </div> <div class="card"> <div class="card-content"> <h3 class="card-title">Card Title</h3> <p class="card-description">This is a card description. This is a card description. This is a card description. This is a card description.</p> </div> </div> |
▼CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | .wrap { display: flex; flex-wrap: wrap; } .card { border-radius: 20px; box-shadow: 0px 0px 10px 0px rgba(51, 51, 51, 0.2); padding: 15px 15px; width: calc((100% - 30px) / 2); } .card:nth-child(2n+2) { margin-left: 30px; } .card-img { border-radius: 10px; overflow: hidden; max-width: 150px; max-height: 150px; } .card-title { font-size: 18px; font-weight: bold; } .card-description { margin-top: 10px; } .card:has(.card-img) { display: flex; flex-wrap: wrap; } .card:has(.card-img) .card-content { margin-left: 15px; width: calc(100% - 165px); } |
ポイントは以下です。
1 2 3 4 5 6 7 8 9 10 | .card:has(.card-img) { display: flex; flex-wrap: wrap; } .card:has(.card-img) .card-content { margin-left: 15px; width: calc(100% - 165px); } |
.cardの中に.card-imgがあるときだけ、.cardの中身を横並びにしています。
それに合わせて、テキストが増えても段落ちしないようにテキストエリアの横幅も画像と余白分を除いた幅を指定します。
これで画像がある場合、中身が横並びになり、テキストの横幅も画像+余白分を除いたサイズになりました。
画像がない場合のテキストは親要素いっぱいの横幅になっていると思います。
特定の要素がある要素の後に続いているかどうか判別する
親要素の中に特定の要素が含まれているか判別できるだけではなく、ある要素の後に特定の要素が続いているかどうかも判別することができます。
デモはこちらです。
:has – 特定の要素がある要素の後に続いているかどうか判別する
▼HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <div class="card"> <div class="card-img"> <img src="./img/card-img01.jpg" alt=""> </div> <div class="card-content"> <h3 class="card-title">Card Title</h3> <p class="card-description">This is a card description. This is a card description. This is a card description. This is a card description.</p> <p class="card-description">This is a card description. This is a card description. This is a card description. This is a card description.</p> </div> </div> <div class="card"> <div class="card-content"> <h3 class="card-title">Card Title</h3> <p class="card-description">This is a card description. This is a card description. This is a card description. This is a card description.</p> <a href="" class="card-link">The link will be entered.</a> </div> </div> |
▼CSS(※追加された部分だけ記載しています)
1 2 3 4 5 6 7 8 9 10 11 12 | .card-link { color: #00adb5; font-weight: bold; text-decoration: underline; text-underline-offset: 3px; } .card-description:has(+.card-description, +.card-link) { margin-bottom: 10px; } |
ポイントは以下です。
1 2 3 4 5 | .card-description:has(+.card-description, +.card-link) { margin-bottom: 10px; } |
.card-descriptionの後に、.card-descriptionか.card-linkがある時、下余白10pxがつきます。
form要素でフォーカスされたinput要素があるか判別する
form要素でフォーカスされたinput要素があるかどうか判別することも可能です。
デモはこちらです。
:has – form要素でフォーカスされたinput要素があるか判別する
▼HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <div class="table-wrap"> <form action=""> <table> <tr> <th>thタイトル</th> <td> <input type="text"> </td> </tr> <tr> <th>thタイトル</th> <td> <input type="text"> </td> </tr> <tr> <th>thタイトル</th> <td> <input type="text"> </td> </tr> </table> </form> </div> |
▼CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | table { width: 100%; } th, td { padding: 30px; } tr + tr th, tr + tr td { padding-top: 0; } th { text-align: left; width: 200px; } input { border: 1px solid #eee; padding: 10px; width: 100%; } form { border-radius: 20px; box-shadow: 0px 0px 10px 0px rgba(51, 51, 51, 0.2); } form:has(input:focus) { box-shadow: 0px 0px 10px 0px rgba(0, 173, 181,0.2); } |
ポイントは以下です。
1 2 3 4 5 | form:has(input:focus) { box-shadow: 0px 0px 10px 0px rgba(0, 173, 181,0.2); } |
form要素に含まれたinputがフォーカスされたら、form要素のドロップシャドウを青色に変更しています。
対応ブラウザ
:has()セレクタのサポート状況は以下です。
2023年6月現在は、Chrome・Edge・Safari・Operaでサポートされています。
:is()
:is()セレクタは、指定した要素に一致する場合にCSSを適用できます。
特定の要素をまとめて選択し、一括でスタイルを指定することができるので、冗長なコードを短くすることができます。
■参考ドキュメント
:is() | MDN
使用例
デモはこちらです。
:is – まとめて指定
具体的な書き方は以下の通りです。
1 2 3 4 5 | :is(セレクタ, セレクタ, セレクタ) { /* スタイルの宣言 */ } |
例えばh1、h2、h3の色を青色にしたい時、それぞれCSSを書いていくと同じ内容が複数になり、コードが冗長になってしまいます。
そこで:is()セレクタを使ってまとめて指定することでコードを短く綺麗にまとめることができます。
1 2 3 4 5 6 7 8 9 10 11 | h1 { color: #00adb5; } h2 { color: #00adb5; } h3 { color: #00adb5; } |
▲ 同じ記述が続いて長い…。
1 2 3 4 5 | :is(h1, h2, h3) { color: #00adb5; } |
▲ たった3行!
Scssを使っている場合
CSSで:is()セレクタを使用した場合とScssでネストを使用した場合、表示上の得られる結果は同じです。
コードの分かりやすさ・管理しやすさの面でいうと、Scssではネストを使用するほうがよいかもしれません。
ただ、Scssのネストで書いた場合、生成されたCSSでは同じセレクタが繰り返されるのでその分冗長になります。
1 2 3 4 5 6 7 | .wrap { h1, h2, h3 { color: red; } } |
▲ Scssのネスト
CSSが生成されると…。
1 2 3 4 5 | .wrap h1, .wrap h2, .wrap h3 { color: red; } |
▲ 要素ごとに.wrapが生成されてしまう
その点、:is()セレクタは、複数の要素をまとめて指定することができます。
1 2 3 4 5 6 7 | .wrap { :is(h1, h2, h3) { color: red; } } |
▲ :is()でまとめる
CSSが生成されると…。
1 2 3 4 5 | .wrap :is(h1, h2, h3) { color: red; } |
▲ .wrapは一つだけ!
Scssから生成されたCSSの無駄を省きたい場合は、:is()セレクタを使ってまとめるのも一つの手です。
対応ブラウザ
:is()セレクタのサポート状況は以下です。
2023年6月現在は、Chrome・Edge・Safari・Firefox・Operaでサポートされています。
:where()
:where()セレクタは、:is()セレクタと同じように特定の要素をまとめて選択し、一括でスタイルを指定することができるセレクタです。
しかし、:is()セレクタと違うのは「詳細度(CSSの優先度)の高さ」です。
:where()セレクタを使うと詳細度が0となり、優先順位が一番低くなるので、スタイルを上書きしたいときに便利なセレクタです。
■参考ドキュメント
:where() | MDN
詳細度については、以下のブログで詳しく紹介しているので合わせてご覧ください。
CSSの詳細度と継承について
使用例
デモはこちらです。
:where – まとめて指定
具体的な書き方は以下の通りです。
1 2 3 4 5 | :where(セレクタ, セレクタ, セレクタ) { /* スタイルの宣言 */ } |
書き方は:is()セレクタと同じです。
1 2 3 4 5 6 7 8 9 10 11 | h1 { color: #00adb5; } h2 { color: #00adb5; } h3 { color: #00adb5; } |
▲ 同じ記述が続いて長い…。
1 2 3 4 5 | :where(h1, h2, h3) { color: #00adb5; } |
▲ たった3行!
Scssを使っている場合
Scssのネストが深くなっていくと、スタイルの詳細度が上がり上書きが難しくなってしまいます。
しかし、:where()セレクタを使用すると詳細度が0になるので、深くなったネストでも上書きすることができます。
1 2 3 4 5 6 7 8 9 | <div class="wrap"> <p class="text"> <a href="/"> リンクが入ります。 </a> </p> </div> |
上記のコードがあったとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // ❶ .wrap { .text { a { color: #00adb5; } } } // ❷ .wrap { a { color: #f06787; } } |
❶.wrap内の.textに含まれるaタグの色は青色
❷.wrap内に含まれているaタグは赤色
という指定すると階層が深いのは❶になるので、.text内のリンクは青文字になります。
しかし、以下のように:where()セレクタを使って書くと階層の深さ関係無く、詳細度が一番低くなるので❷のCSSが当たるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // ❶ .wrap { .text { &:where(a) { color: #00adb5; } } } // ❷ .wrap { a { color: #f06787; } } |
とはいえ、:where()セレクタ使えるからと言ってネストを深くしすぎないように注意が必要です。
状況によって使い分けて行くことが大事です。
対応ブラウザ
:where()セレクタのサポート状況は以下です。
2023年6月現在は、Chrome・Edge・Safari・FireFox・Operaでサポートされています。
:is()と:where()の違い
先ほど詳細度の話が出てきましたが、実際に詳細度での違いというのがどのように影響するのか見てみたいと思います。
同じ構造のHTMLに、それぞれ:is()、:where()でCSSを指定してみましょう。
デモはこちらです。
:is()と:whereの違い
例えば、以下のようなコードがあるとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <div class="is-wrap"> <div class="top"> <p class="text"> テキストテキスト<a href="">リンクが入ります。</a> </p> </div> <div class="center"> <p class="text"> テキストテキスト<a href="">リンクが入ります。</a> </p> </div> <div class="bottom"> <p class="text"> テキストテキスト<a href="">リンクが入ります。</a> </p> </div> </div> <div class="where-wrap"> <div class="top"> <p class="text"> テキストテキスト<a href="">リンクが入ります。</a> </p> </div> <div class="center"> <p class="text"> テキストテキスト<a href="">リンクが入ります。</a> </p> </div> <div class="bottom"> <p class="text"> テキストテキスト<a href="">リンクが入ります。</a> </p> </div> </div> |
.is-wrapには:is()を使って、.where-wrapには:where()を使ってリンクの色を指定します。
1 2 3 4 5 6 7 8 | .is-wrap :is(.top, .center, .bottom) a { color: #00adb5; } .where-wrap :where(.top, .center, .bottom) a { color: #f06787; } |
指定した色にリンクの色が変わりました。
しかし途中で.bottomのリンクだけ色を上書きたい、と最後のリンクの色を上書きするCSSを書くとどうなるでしょう?
1 2 3 4 5 | .bottom a { color: #95b500; } |
結果上書きされたのは、.where-wrapの.bottomに含まれるリンクだけなのがわかります。
最後のリンクの色を上書きするCSSの詳細度が:where()セレクタの詳細度より高いので、.where-wrapの.bottomに含まれるリンクだけ緑色に変わりました。
まとめ
本記事では、CSSの最新バージョンで追加された便利な新セレクタ、:has()、:is()、:where()の使い方についてデモサイトを交えて紹介しました。
これらを上手に使うことで、コードを短く保ちながら柔軟性を高めることができます。
今回ご紹介したのは一例ですが、他にも様々な使用方法があると思います。
セレクタを使いこなして、効率的かつ柔軟なスタイリングを実現しましょう!