CSS の疑似クラス、:is(), :where(), :not(), :has() の使い方と実用例

2023年に Firefox で :has() がサポートされ、実務でも使用されることが増えてきた CSS の擬似クラス。うまく活用することで、これまで JavaScript を使って実装していたようなことが、CSS 単体でも実現できるようになりました。

しかし、まだ使い方がよくわからない、うまく活用できている気がしないという方も多いのではないでしょうか?

本記事では、擬似クラス :is():where():not():has() の基本的な使い方を解説し、実際にどのような場面で活用できるのか、実用例を交えて紹介します。

LINE着せかえ「回るお寿司」販売中!

疑似クラス :is(), :where(), :not(), :has() の使い方

:is()

擬似クラス :is() は、指定したセレクターのいずれかに該当する要素にスタイルを適用できます。

例えば、以下のように記述すると、<ul> タグまたは <ol> タグ内の <li> タグにスタイルが適用されます。

:is(ul, ol) li {
  color: red;
}

See the Pen CSS Pseudo Classes :is() 01 by YUTSUZO (@YUTSUZO) on CodePen.

:where()

擬似クラス :where() も、指定したセレクターのいずれかに該当する要素にスタイルを適用できます。

例えば、以下のように記述すると、<ul> タグまたは <ol> タグ内の <li> タグにスタイルが適用されます。

:where(ul, ol) li {
  color: red;
}

これだけ見ると :is() と同じように見えますが、:is() と異なり、:where() 内で指定したセレクターは詳細度(CSS の優先度)に影響を与えません。

例えば、:is() 内で指定したセレクターに適用されたスタイルを後から上書きするには、ol li のように詳細度を同等以上にする必要があります。

See the Pen Untitled by YUTSUZO (@YUTSUZO) on CodePen.

それに対して、:where()で指定されたセレクタに適用されたスタイルは、詳細度を高めなくても上書きされます。

See the Pen CSS Pseudo Classes :where() 02 by YUTSUZO (@YUTSUZO) on CodePen.

そのため、後から上書きすることが多いベーススタイルなどの指定には、:is() よりも :where() のほうが適していると言えるでしょう。

:not()

擬似クラス:not()は、指定されたセレクターを除外した要素にスタイルを適用できます。

例えば、以下のように書いた場合、「最初の要素以外の<li>タグ」に対してスタイルが適用されます。

li:not(:first-child) {
  color: red;
}

See the Pen CSS Pseudo Classes :not() by YUTSUZO (@YUTSUZO) on CodePen.

:has()

擬似クラス:has()は、指定されたセレクターを子要素に持つ場合や、直後に持つ場合にスタイルを適用できる、二つの使い方があります。

まず、子要素に持つ場合についてですが、以下のように書いた場合は、「子要素に<span>タグを持つ<li>タグ」に対してスタイルが適用されます。

li:has(span)  {
  color: red;
}

See the Pen CSS Pseudo Classes :has() 01 by YUTSUZO (@YUTSUZO) on CodePen.

そして、直後に持つ場合についてですが、以下のように書いた場合は「直後に<ol>タグを持つ<ul>タグ」にスタイルが適用されます。

ul:has(+ol) {
  color: red;
}

See the Pen CSS Pseudo Classes :has() 02 by YUTSUZO (@YUTSUZO) on CodePen.

疑似クラス :is(), :where(), :not(), :has() の実用例

:where() を使ってベースのスタイルを設定する

ブログサイトなどでは、記事内の<p><ul>,<ol>などのブロックレベル要素の後に自動で余白をつけるために、要素型セレクタを用いてまとめてmarginを設定するようなことがあります。

そのような時に、カンマ区切りで複数のセレクタを指定した場合、以下のような問題が発生することがあります。

See the Pen CSS Pseudo Classes Sample 01 by YUTSUZO (@YUTSUZO) on CodePen.

ユーティリティクラスなどを使って、特定の箇所だけ余白を大きくしようと思っても、優先度が低いために適用されないといったケースです。

一方、:where() を使用した場合は、ユーティリティクラスが正しく適用されていることが確認できます。

See the Pen CSS Pseudo Classes Sample 02 by YUTSUZO (@YUTSUZO) on CodePen.

:where() を使うことで、優先度が低いままベースのスタイルを記述することができるのです。

:is() と :not() を組み合わせて複雑な条件に当てはまる要素にスタイルをあてる

ウェブサイト全体で、以下のようなベーススタイルを設定しているとします。

.article :where(p, ul, ol) {
  margin: 0 0 30px 0;
}

すると、このような問題が発生することがあります。

See the Pen Untitled by YUTSUZO (@YUTSUZO) on CodePen.

<p>タグのmarginのせいで、.boxの下側だけ余白が大きくあいてしまっています。

そのような場合、:is() を使用して以下のような CSS を追加すると、最後の <p> タグの後の margin をなくすことができます。

.article *:last-child:is(p, ul, ol) {
  margin-bottom: 0;
}

See the Pen CSS Pseudo Classes 04 by YUTSUZO (@YUTSUZO) on CodePen.

*:last-child という指定があるのは、.box以外にも.note.cautionなど、様々なボックスタイプのコンポーネントがあることを想定しているためです。

それらのコンポーネントの最後の要素が <p><ul><ol> タグで終わっている場合、その要素に対して margin-bottom: 0; が適用されます。さらに、これらの要素がコンポーネント内ではなく、.article の直接の子要素である場合にも適用されます。

また、ユーティリティクラスなどを使用して明示的に margin を空けたい場合は、:not() を併用することで、特定のクラスが付いている場合に適用されないようにすることも可能です。

See the Pen CSS Pseudo Classes 05 by YUTSUZO (@YUTSUZO) on CodePen.

:has() を使って、子要素の状態に応じてスタイルを変える

:has() を使うと、子要素が特定の状態にある場合にのみスタイルを適用することができます。例えば、以下のような CSS を記述すると、disabled状態のinputを持つlabel要素をグレーアウトさせることができます。

label:has(input[disabled]) {
  color: #ccc;
}

See the Pen CSS Pseudo Classes 06 by YUTSUZO (@YUTSUZO) on CodePen.

終わりに

CSSの擬似クラス :is(),:where(),:not(),:has() を使用することで、より効率的で柔軟なスタイルの適用が可能になります。これらを適切に使い分けることで、JavaScriptを使わずに複雑な条件を CSS で実現でき、コードの可読性や保守性も向上します。ぜひ、活用してみてください。

参考