[CSS] 全ブラウザでサポートされた:has擬似クラスの使い方

2023年12月19日に行われた FireFox のアップデートにより、すべてのブラウザで:has擬似クラスが使用できるようになりました。これは、CSSの歴史において革命的な出来事です。

今回は、この:has擬似クラスの革命的な理由を解説すると共に、使い方も紹介していきます。

:has擬似クラスが革命的すぎる理由

/* ある要素の子孫要素 */
.A .B { … }

/* ある要素の子要素 */
.A > .B { … }

/* ある要素の次の要素 */
.A + .B { … }

/* ある要素に隣接する要素 */
.A ~ .B { … }

これまでのCSSセレクタは、親要素の中の子孫要素 (.A .B, .A > .B)、先行する要素に後続する要素 (.A + .B, .A ~ .B) のように、常に一定の方向 (.A から .B の順) でしか設定できませんでした。

/* ある子孫要素がある要素 */
.A:has(.B) { … }

/* ある子要素がある要素 */
.A:has(> .B) { … }

/* ある要素の前の要素 */
.A:has(+ .B) { … }

/* ある要素が隣接する要素 */
.A:has(~ .B) { … }

しかし、:has擬似クラスが登場したことで、逆の方向 (.B から .A の順) での設定が可能になりました。

/* 8個が隣接する要素すべて */
:first-child:nth-last-child(8),
:first-child:nth-last-child(8) ~ * { … }

/* 8個の子要素がある要素 */
:has(> :last-child:nth-child(8)) { … }

また、決まった個数で隣接する要素自体は今までも設定できましたが、:has擬似クラスを使用することで、決まった個数の子要素がある要素を設定できるようになりました。

コンテナ要素と、その子要素のアイテム要素からなる、FlexレイアウトやGridレイアウトの設定がより便利になりそうです。

すべての擬似クラスを “within” にする

/* input要素を内包するlabel要素 */
label.has-input { … }

/* フォーカス状態になったinput要素を内包するlabel要素 */
label.has-input:focus-within { … }

:focus-within擬似クラスというものがあります。これは子孫要素がフォーカス状態になった要素を設定できます。:has擬似クラスが登場する前からある、子孫要素の状態でその祖先要素が設定できた唯一の擬似クラスです。

:target-within擬似クラスというのも策定途中の仕様にはありますが、2023年12月現在、使用できるブラウザはありません。しかし、:has擬似クラスを使用することで、:target-within擬似クラスを実現できるだけでなく、その他の擬似クラスの “within” も実現できます。

/* :focus-withinの代替 */
:has(:focus) { … }

/* 明示すべきフォーカス状態になった要素を内包する要素 = :focus-visible-within */
:has(:focus-visible) { … }

/* ターゲットになった要素を内包する要素 = :target-within */
:has(:target) { … }

/* リンクを内包する要素 = :any-link-within */
:has(:any-link) { … }

/* 未訪問リンクを内包する要素 = :link-within */
:has(:link) { … }

/* 訪問済みリンクを内包する要素 = :visited-within */
:has(:visited) { … }

/* ホバー状態になった要素を内包する要素 = :hover-within */
:has(:hover) { … }

/* アクティブ状態になった要素を内包する要素 = :active-within */
:has(:active) { … }

/* チェック状態になった要素を内包する要素 = :checked-within */
:has(:checked) { … }

etc...

:has(:target) を使用したタブ切り替えの例

See the Pen Tabs:target-within by nov (@numerofive) on CodePen.

これまで、要素のclass属性に「has-XXXX」といった値を埋め込んだり、JavaScript で要素の状態を監視して、class属性値を付けたり外したりしていましたが、:has擬似クラスで、そんな日々とはサヨナラ。

関連記事