2023年12月19日からFirefoxでも:has()が使えます

# はじめに

この記事は [2023 年の MDN 翻訳 Advent Calendar](https://qiita.com/advent-calendar/2023/mdn-translation-community-japan) 向けに作成したものです。

こんにちは。debiru です。HTML との最初の触れ合いは Windows 95 を使って Yahoo! ジオシティーズの Hollywood スペースに Web サイトを開設した頃です。

ええと、今日は Safari (+ iOS Safari) では 2022 年 3 月に、Opera, Edge, Chrome では 2022 年 9 月にサポートされ、もちろん Vivaldi でも使える `:has()` 疑似クラスが Firefox で長年（長年？）サポートされなくてもやもやしていた皆さんに朗報です。

2023 年 12 月 19 日リリース予定の Firefox 121 で `:has()` がサポートされるというお話をしに来ました。

# みんな大好き `:has()` 疑似クラス

疑似は擬似（手偏あり）ではなく疑似（手偏なし）と書きましょう（[2015年振り2回目の国語のお話](https://debiru.hatenablog.com/entry/20150920/css-pseudo-element#kanji)）。

さて、CSS マニアなみなさんならご存知かと思いますが `:has()` 疑似クラスというものがあります。

`:has()` 疑似クラスが登場する前は、セレクタ間の関係によってマッチする CSS セレクタというのは、

- よく使われる子孫セレクタ（`div p { div の子孫の p にマッチ }`）
- 子セレクタ（`div > p { div 直下の p にマッチ }`）
- いわゆる隣接セレクタ（`div + p { div 直後の弟の p にマッチ }`）
- いわゆる間接セレクタ（`div ~ p { div に後続する弟の p にマッチ }`）

```
<div>
  <p>div > p の子セレクタでマッチ（もちろん子孫セレクタでもマッチ）</p>
  <section>
    <h1>私はページ内に h1 が複数あってもよいのではないかと常々思う</h1>
    <p>div p の子孫セレクタでマッチ</p>
  </section>
</div>
<p>div + p の隣接セレクタでマッチ</p>
<p>div ~ p の間接セレクタでマッチ</p>
<anotherElement>他の要素があっても</anotherElement>
<p>div ~ p の間接セレクタでマッチ</p>
```
 
くらいしかありませんでした。

## 余談：隣接セレクタと間接セレクタの正式名称

以前（改名前）は、隣接セレクタ（`+`）の方は、`adjacent-sibling-combinators`（隣接兄弟結合子）と呼ばれ、間接セレクタ（`~`）の方は `general-sibling-combinators`（一般兄弟結合子）と呼ばれていました。日本語訳は MDN での呼称です。

ところが 2017 年 5 月に、こんなスレが立ちました。

https://github.com/w3c/csswg-drafts/issues/1382

改名が提案され CSS 仕様書では、隣接セレクタ（`+`）は `Next-sibling combinator` に、間接セレクタ（`~`）は `Subsequent-sibling combinator` に改名されました。そして、2023 年 10 月 29 日に MDN 日本語訳では「次兄弟結合子」と「後続兄弟結合子」という名称になりました。

https://twitter.com/debiru_R/status/1718987846651109727

## 話を戻して、`:has()` 疑似クラスでできること

例えば、「`figcaption` を持っている `figure` 要素だったら」みたいなセレクタを書くことができるようになります。

```css
figure:has(> figcaption) { ... }
```

あるいは、JavaScript を使わずに CSS だけで状態管理をするのにラジオボタンやチェックボックスの `:checked` 疑似クラスを使うテクニックがありますが、これまでは隣接・間接セレクタでしか表現できなかったので、こんな HTML 構造にせざるを得ませんでした。

```html
<div class="container">
  <label for="flag-1">フラグ1</label><input id="flag-1" type="checkbox">
  <div class="content">
    ここにコンテンツがあって、フラグ1の状態によって色を変えたり表示状態を変えたりできる
  </div>
</div>
```

```css
#flag-1:checked + .content .hogehoge { display: none; }
```

これを、`:has()` を使うと、HTML 構造を（隣接・間接セレクタに縛られずに）自由にすることができるようになります。（以下は `input` が `label` の中に入っただけ。）

```html
<div class="container">
  <label>フラグ1<input id="flag-1" type="checkbox"></label>
  <div class="content">
    ここにコンテンツがあって、フラグ1の状態によって色を変えたり表示状態を変えたりできる
  </div>
</div>
```

```css
.container:has(#flag-1:checked) .content .hogehoge { display: none; }
```

`:has()` 疑似クラスによって自由度が非常に上がっていることが理解できるでしょうか。`:has()` 疑似クラスは、子孫セレクタ、子セレクタ、隣接セレクタ、間接セレクタに次ぐ**大発明**なのです。

そういえば `:not()` 疑似クラスなんていうものもありますね。ついでに紹介しておきます。私はよく次の形で `:not()` 疑似クラスを使います。

```css
.content > *:not(:first-child) {
  margin-top: 10px;
}
```

ボックスの子要素に上マージンを付けたいけれど、最初の要素には上マージンは要らないという指定ですね。

まあ、とにかく、みなさん待望の `:has()` 疑似クラスが、MDN の「ブラウザーの互換性」の表にある全てのブラウザでサポートされるのです。

https://developer.mozilla.org/ja/docs/Web/CSS/:has

![MDN のブラウザーの互換性一覧表。Chrome, Edge, Firefox, Opera, Safari の全てのブラウザで :has() 疑似クラスがサポートされていることが分かる。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178936/784eb83a-ffdc-b497-35a9-fcf05a6323e2.png)

# さいごに

`:has()` 疑似クラスのサポートによって実現できることとして、JavaScript を使わなくとも表現度の高い CSS が記述できるようになるわけですが、もしかすると JavaScript を一切使わずに状態を表現したコンテンツ作りがしやすくなるかもしれません。

まず思い浮かぶのがみなさんご存知の [CSS PANIC](https://geckotang.github.io/presentation-uit-20190724/) です。よく考えると、`:has()` がサポートされたからといって状態表現の幅が広がるわけではない（間接セレクタだけでも同等の表現力がある）のですが、少なくとも HTML 構造をシンプルにすることができるようになるでしょう。`:has()` 疑似クラスを活かした CSS PANIC に次ぐ CSS コンテンツが登場するであろうことが容易に想像できます。

ところで、本当はこのネタは Firefox 121 がリリースされた 12 月 19 日の [2023 年の MDN 翻訳 Advent Calendar](https://qiita.com/advent-calendar/2023/mdn-translation-community-japan) として公開しようかとも思ったのですが、いかんせんアドベントカレンダーを誰も書かないので、12 月 4 日の記事として書くことにしました。Firefox がリリースされてから気づくよりも予め知っていたほうがみなさんもいいでしょ？

ということで `:has()` 疑似クラスによって CSS の表現力が爆上がりする話でした。

おわり。