:has()擬似クラス

:has()疑似クラスを使用すると、親要素に特定の子要素があるか、またはその子要素が特定の状態であるかに基づいてスタイルを適用することができる。つまり、実質的に親セレクタを持つことになる。コンテナクエリと相性が良い。
2023/12/19にリリースされたFirefox 121(リリース情報)でサポートされ、これで:has()疑似クラスがすべてのブラウザにサポートされた。
現在のサポート状況(Can I Use)

今までのCSSでは、要素の存在のあり・なしによって特定の親や要素にスタイルを設定することは不可能だった。あり用となし用のクラスを作成し、必要なバリエーションに応じて切り替える必要があった。

/* 画像あり用のスタイル */
.card {
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* 画像なし用のスタイル */
.card--plain {
  display: block;
  border-top: 3px solid #7c93e9;
}
<!-- 画像ありのカード -->
<div class="card">
  <div class="card__image">
    <img src="awameh.jpg" alt="">
  </div>
  <div class="card__content">
    <!-- Card content here -->
  </div>
</div>

<!-- 画像なしのカード -->
<div class="card card--plain">
  <div class="card__content">
    <!-- Card content here -->
  </div>
</div>

画像なしにはFlexboxが必要ないため、なし用とあり用の2つのバリエーションのクラスを作成する必要がある。
:has()を使用すると、.card要素の中に.card__imageがあるかどうかをチェックすることができる。
たとえば、カードに画像があるかどうかをチェックし、ある場合にはFlexboxで配置できる。

.card:has(.card__image) {
  display: flex;
  align-items: center;
}

:has()疑似クラスの基本的な使い方

:has()疑似クラスを使用すると、子の数に基づいて親要素のスタイルを設定することができる。

/* 最大3個の子(0を除く3以下) */
ul:has(> :nth-child(-n+3):last-child) {
  outline: 1px solid red;
}

/* 最大3個の子(0を含む3以下) */
ul:not(:has(> :nth-child(3))) {
  outline: 1px solid red;
}

/* ちょうど5個の子 */
ul:has(> :nth-child(5):last-child) {
  outline: 1px solid blue;
}

/* 10個以上の子 */
ul:has(> :nth-child(10)) {
  outline: 1px solid green;
}

/* 7~9個の子(境界を含む) */
ul:has(> :nth-child(7)):has(> :nth-child(-n+9):last-child) {
  outline: 1px solid yellow;
}

:has()疑似クラスの便利な使い方

:has()疑似クラスを使用するとさまざまなセレクタを条件式のように記述できる。

/* figcaptionがあるfigure要素を選択 */
figure:has(> figcaption) { ... }

/* svgの直接の子孫がないa要素を選択 */
a:not(:has(> svg)) { ... }

/* inputの直接の兄弟があるlabel要素を選択 */
label:has(+ input) { … }

/* 子孫のimgにaltがないarticle要素を選択 */
article:has(img:not([alt])) { … }

/* DOM内で何らかの状態が存在するdocumentElementを選択 */
:root:has(.menu-toggle[aria-pressed=”true”]) { … }

/* 子の数が奇数の.containerを選択 */
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }

/* ホバーされていないグリッド内のすべてのアイテムを選択 */
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }

/* カスタム要素<todo-list>を含むコンテナを選択 */
main:has(todo-list) { ... }

/* 直接のhrの兄弟があるp要素内のすべてのa要素を選択 */
p:has(+ hr) a:only-child { … }

/* 複数の条件を満たすarticle要素を選択 */
article:has(>h1):has(>h2) { … }

/* h1の後にh2が続くarticle要素を選択 */
article:has(> h1 + h2) { … }

/* インタラクティブな状態が発生したときに:rootを選択 */
:root:has(a:hover) { … }

/* figcaptionがないfigureに続くp要素を選択 */
figure:not(:has(figcaption)) + p { … }

フォームの実装

:has()疑似クラスを使用すると、:validや:invalidなど、関連するフォームの疑似クラスをフックにできる。

/* ブラウザのバリデーションでエラーになった場合 */
.form-group:has(:invalid) {
  --color: var(--invalid);
}
/* エレメントが入力待ちまたは操作待ちになった場合 */
.form-group:has(:focus) {
  --color: var(--focus);
}
/* ブラウザのバリデーションで有効になった場合 */
.form-group:has(:valid) {
  --color: var(--valid);
}
/* placeholder が表示されているときのみ有効にしたい場合 */
.form-group:has(:placeholder-shown) {
  --color: var(--blur);
}
/* フィールドが:invalidになり、かつフォーカスされていない場合、.form-group__errorを表示する */
.form-group:has(:invalid:not(:focus)) .form-group__error {
  display: block;
}

大規模なDOMツリーでのレンダリングパフォーマンスの低下を防ぐためには、この:has()セレクタのスコープをできるだけ厳密にすることを推奨する。たとえば、:has()をルートのhtml要素で使用すると、ナビゲーションバーや小さなツリーのカード要素で一致をチェックするよりも遅くなってしまう。

【参考】
https://coliss.com/articles/build-websites/operation/css/has-pseudo-class.html
https://coliss.com/articles/build-websites/operation/css/container-queries-and-has-demo.html

inserted by FC2 system