CSS :has() 親セレクタ徹底活用:JavaScriptレスの状態管理

CSS :has() 親セレクタ徹底活用:JavaScriptレスの状態管理

CSSの世界に、まさに「革命」と呼べる新しい機能が登場しました。それが、親セレクタとも呼ばれる:has()擬似クラスです。

これまで「子要素の状態に応じて親要素のスタイルを変える」といったことは、JavaScriptを使わなければ実現できませんでした。しかし、:has()の登場により、CSSだけで多くのインタラクティブな表現が可能になったのです。

この記事では、:has()の基本的な使い方から、JavaScriptを使わずに状態を管理する具体的なテクニックまで、分かりやすく解説していきます。CSSの可能性を大きく広げるこの新機能を、ぜひマスターしましょう。

目次

CSSの親セレクタ:has()とは?

CSSの:has()は、特定の要素を「子孫に持つかどうか」を条件に、要素を選択できる新しい擬似クラスです。これまでCSSセレクタは、親から子へ、または兄弟要素へと一方向にしか指定できませんでした。しかし:has()は、子や孫の要素を基準にして、その親や先祖要素にスタイルを適用できるため、「親セレクタ」と呼ばれています。

例えば、section:has(h2)というセレクタは、「<h2>要素を子孫に持つ<section>要素」を選択します。これにより、見出しがあるセクションとないセクションで、背景色や余白を変えるといったデザインの出し分けがCSSだけで完結します。

この機能は非常に画期的で、多くのモダンブラウザでサポートが始まっています。Chrome、Safari、Edge、Firefoxといった主要なブラウザの最新版では、すでに利用可能です。これまでJavaScriptに頼らざるを得なかった多くの場面で、CSSがその役割を担えるようになるため、Web制作の常識を大きく変えるポテンシャルを秘めていると言えるでしょう。今後のWeb標準の進化を象徴する機能の一つです。

参考::has() – CSS: Cascading Style Sheets | MDN

:has()はなぜ画期的なのか?JavaScriptが不要になるケース

:has()が画期的とされる最大の理由は、これまでJavaScriptの独壇場だった「状態の管理」をCSSだけで実現できる点にあります。Webページ上の要素は、ユーザーの操作やコンテンツの有無によって状態が変化します。例えば、「フォームの入力欄が有効か」「特定の画像が含まれているか」といった状態です。

従来、このような状態を検知して親要素の見た目を変えるには、JavaScriptでDOMを監視し、クラスを付け外しする処理が必要でした。例えば、画像を含むカードだけ枠線を太くしたい場合、JavaScriptで<img>タグの存在を確認し、その親であるカード要素に.has-imageのようなクラスを追加していたのです。

しかし:has()を使えば、div.card:has(img)というセレクタだけで、同じことが実現できます。JavaScriptのコードが不要になることで、主に2つのメリットが生まれます。1つ目はパフォーマンスの向上です。CSSによるスタイリングは、JavaScriptによるDOM操作よりも高速に処理されるため、ページの表示速度や応答性が改善します。2つ目は、コードの簡潔化とメンテナンス性の向上です。スタイルに関する記述がCSSファイルに集約されるため、コードの見通しが良くなり、管理も楽になります。

:has()の具体的な使い方と活用事例

理屈は分かっても、実際にどう使うのかイメージが湧きにくいかもしれません。ここでは、:has()を使った具体的な活用事例を3つのパターンに分けて、コードと共に紹介します。

フォームの入力状態に応じたスタイル変更

フォームの使いやすさは、ユーザー体験に直結する重要な要素です。:has()を活用すると、入力フィールドの状態に応じて、ラベルや説明文のスタイルを動的に変更できます。

例えば、required(必須)属性が付いた<input>が未入力(:placeholder-shown)の場合に、親要素である<div>のラベルの色を変えて注意を促す、といった実装が可能です。

/* 必須入力欄で、まだ入力されていない場合 */
.form-field:has(input[required]:placeholder-shown) {
  border-left: 3px solid #f44336; /* 赤いボーダーで注意喚起 */
}

/* 有効な入力がされた場合 */
.form-field:has(input:valid) {
  border-left: 3px solid #4caf50; /* 緑のボーダーでOKを示す */
}

このように、JavaScriptで入力値をチェックしなくても、CSSだけでユーザーにリアルタイムなフィードバックを提供できます。ユーザーはどの項目を修正すべきか直感的に理解でき、フォームの離脱率低下にも繋がるでしょう。

特定の要素の有無でレイアウトを切り替える

ブログのカード型レイアウトなどで、アイキャッチ画像がある記事とない記事が混在するケースはよくあります。画像がないカードは、テキストが間延びして見えたり、レイアウトが崩れたりしがちです。

:has()を使えば、<img>要素の有無を条件に、スタイルを簡単に切り替えられます。

.card {
  display: flex;
  gap: 1rem;
}

/* カード内にimg要素がない場合 */
.card:not(:has(img)) {
  background-color: #f0f8ff; /* 背景色を変える */
  align-items: center; /* テキストを中央揃えにする */
}

/* カード内にimg要素がある場合 */
.card:has(img) .card-body {
  flex: 1; /* テキスト部分の幅を調整 */
}

この例では、画像がないカード(:not(:has(img)))の背景色を変え、テキストを中央に配置しています。これにより、画像がなくてもデザイン的なまとまりが生まれ、コンテンツの均質性を保つことが可能です。わざわざ画像なし用の別コンポーネントを用意する必要がなくなり、HTML構造をシンプルに保てるのも大きなメリットです。

インタラクティブなUIの実装(チェックボックスの活用)

チェックボックスの状態(:checked)をトリガーにすることで、JavaScriptなしで様々なUIを実装できます。ハンバーガーメニューの開閉などがその代表例です。

従来はJavaScriptでクリックイベントを監視していましたが、:has()と組み合わせることで、より高度なインタラクションも可能になります。例えば、設定パネル内で特定のチェックボックスがオンになった時だけ、ページ全体のテーマカラーを変更する、といったことも実現できます。

/* チェックボックスがONになったら、bodyに特定のクラスを付けるイメージ */
body:has(#dark-mode-toggle:checked) {
  background-color: #333;
  color: #eee;
}

body:has(#dark-mode-toggle:checked) a {
  color: #87cefa;
}

このコードは、IDがdark-mode-toggleのチェックボックスがチェックされた状態の<body>を選択します。つまり、チェックボックスをオンにするだけで、ダークモードへの切り替えが完了するのです。これはあくまで一例ですが、:has()とフォーム要素の状態を組み合わせることで、CSSで表現できることの幅が格段に広がることを示しています。

:has()と他のセレクタの比較

:has()がどれだけ特殊なセレクタか、他の主要なセレクタと比較するとより理解が深まります。それぞれのセレクタが「何を選択するか」に注目して、その違いを見てみましょう。

セレクタの種類構文例選択される要素指定の方向
:has() (親セレクタ)section:has(p)p要素を子孫に持つsection要素子 → 親
子孫セレクタsection psection要素の子孫であるp要素親 → 子
子セレクタsection > psection要素の直下の子であるp要素親 → 子
隣接兄弟セレクタh2 + ph2要素の直後にあるp要素兄 → 弟
一般兄弟セレクタh2 ~ ph2要素以降にあるすべてのp要素兄 → 弟

この表から分かるように、:has()以外のセレクタはすべて、構造の上から下へ、あるいは先から後へと一方向にしか要素を選択できません。sectionの中のpを指定することはできても、pがあるからといってその親であるsectionを指定することは不可能でした。

:has()はこの制約を打ち破り、初めて子から親へと遡る方向の指定を可能にしたのです。この「遡る」能力こそが、:has()を他のセレクタと一線を画す、革命的な機能たらしめている理由です。これにより、HTMLの構造に依存しすぎることなく、より柔軟で意味的なスタイリングが可能になります。

:has()を使う上での注意点とパフォーマンス

:has()は非常に強力な機能ですが、利用する際にはいくつか知っておくべき注意点があります。

まず、パフォーマンスへの影響です。:has()は、ブラウザがDOMツリー全体を評価し直す必要があるため、単純なセレクタに比べて計算コストが高くなる可能性があります。特に、:has()の中にさらに複雑なセレクタをネストしたり、*(ユニバーサルセレクタ)のような広範囲に及ぶセレクタと組み合わせたりすると、ページの描画速度に影響を与える恐れがあります。例えば、div:has(span:hover)のようなセレクタは、マウスが動くたびに再計算が走るため、避けた方が賢明です。基本的には、パフォーマンスが重視される大規模なページでは、過度な使用に注意し、シンプルな条件で使うことを心がけましょう。

次に、セレクタの詳細度です。:has()自体は詳細度に影響を与えませんが、引数として渡されるセレクタリストの中で、最も詳細度が高いものがそのセレクタ全体の詳細度として計算されます。意図しないスタイルで上書きされないよう、詳細度の仕組みを理解しておくことが重要です。

最後に、古いブラウザへの対応です。:has()は比較的新しい機能のため、Internet Explorerはもちろん、数年前のバージョンのブラウザではサポートされていません。業務で利用する場合は、プロジェクトの対象ブラウザを必ず確認してください。機能の性質上、ポリフィル(代替機能)で完全に再現するのは難しいため、現時点ではモダンブラウザを対象とした制作で活用するのが現実的と言えるでしょう。

Geekly(ギークリー)は本当にやばい?評判・口コミから見るIT転職のリアル【2025年最新版】

まとめ

CSSの親セレクタ:has()は、Web制作における長年の課題であった「子要素に基づく親要素のスタイリング」を可能にする、画期的な機能です。

この記事で解説したように、:has()を活用することで、

  • JavaScriptに頼らずに、CSSだけで状態管理ができる
  • フォームの入力状態や要素の有無に応じた動的なスタイル変更が可能になる
  • コードが簡潔になり、パフォーマンスとメンテナンス性が向上する

といった多くのメリットが得られます。

もちろん、パフォーマンスへの配慮やブラウザの対応状況など、注意すべき点もあります。しかし、そのポテンシャルは非常に大きく、これからのCSSコーディングのスタンダードになっていくことは間違いないでしょう。

まずは簡単なところからでも、ぜひ実際のコードで:has()の力を体感してみてください。きっと、CSSでできることの多さに驚くはずです。

CSS Container Queries 超入門:@container で“部品単位のレスポンシブ”へ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次