CSS :has()の使い方|親要素を「中身」で選ぶ。JavaScriptを置き換える実装パターン

eye 19

この記事でわかること
  • :has()で具体的に何ができる?(1行で)
  • JavaScriptを置き換えられるのは、どんな場面?
  • 2026年、どのブラウザで使える?/つまずく注意点は?

:has()は、親要素を「中に何があるか」で選べるCSSの擬似クラスです。たとえば「画像を含むカードだけ枠線をつける」は、これだけで書けます。

CSS
.card:has(img) {
  border: 2px solid blue;
}

これまで「子要素の状態に応じて親要素を変える」にはJavaScriptが必要でした。:has()は、それをCSSだけで完結させます。長らく「CSSには親セレクタがない」と言われてきましたが、その答えがこれです。

2023年12月のFirefox対応で全モダンブラウザがサポートし、2026年現在はBaseline(実務で安心して使える状態)です。私もWeb制作17年・個人開発の中で、フォームやカードUIの出し分けに普通に使っています。この記事では、基本の書き方から実務で効くユースケース、JavaScriptとの比較、つまずきやすい注意点(unforgiving・パフォーマンス)までまとめます。

記事を書いている人

profile

R(アール)

Web制作の現場で17年(現役進行中)。精密栄養カウンセラー。

個人開発をアプリ6本並行しながら、AIと「作る・届ける」を実験しています。

うまくいったことも、月収2,000円みたいな冴えない数字も、隠さず公開中。

教える人ではなく、少し先で転んで戻ってきた人として、あなたと同じ目線で現在地を観測していけたらと思います。


AIと「作る・届ける」の実験は、週1でメルマガにも書いています。→ のぞいてみる(限定特典つき無料)

02 21

対応状況はこちらで確認できます: Can I use :has()

:has()とは? 親要素を「中身」で選べる擬似クラス

まず結論から。:has()は「指定した要素を内側に持つ親要素」を選ぶためのセレクタです。読み方はシンプルで、A:has(B) なら「Bを含むA」を指します。

従来のCSSは「親 → 子」の方向にしか効きませんでした。子要素の状態(チェックされた、画像がある、入力が不正など)に応じて親側を変えたいとき、これまではJavaScriptで監視するしかなかったわけです。:has()は、この「子 → 親」をCSSだけで可能にしました。

とりさん
とりさん

親要素を選ぶって、今までCSSだけじゃ無理だったやつですよね?

R
R

そうです。長年の悲願でした。jQueryの :has をCSSでやりたい、というのはずっと言われていて、今はもう標準で普通に使えます。

:has()の基本的な書き方

基本構文はこうです。

CSS
親要素:has(子要素) {
  /* 親要素に適用するスタイル */
}

実際の例を見てみます。.card の中に <img> があるときだけ、.card に青いボーダーをつけます。

CSS
.card:has(img) {
  border: 2px solid blue;
}

See the Pen :has()_01 by R (@rkpg) on CodePen.

スタイルが当たるのは img ではなく、それを内側に持つ .cardです。「子の有無で親を装飾する」のが、:has() の基本的な発想になります。

実務で効く :has() のユースケース

ここからは、私が実際に個人開発やWeb制作で使っている、効くパターンを紹介します。

① フォームの入力状態で親を変える

チェックボックスがチェックされたら、その親要素の背景色を変える。状態に応じたUIの出し分けが、CSSだけで完結します。

CSS
.form-group:has(input[type="checkbox"]:checked) {
  background-color: Aquamarine;
}

See the Pen Untitled by R (@rkpg) on CodePen.

② 必須項目の未入力・エラーを親側で警告する

入力が不正な要素を含む行を、まとめて警告表示にする。:invalid と組み合わせると、バリデーションの見た目をCSSで作れます。

CSS
.form-group:has(input:invalid) {
  border-left: 4px solid #e3342f;
}

③ 直後に特定要素が続く見出しの余白を詰める

:has() は子だけでなく、隣接(+)も書けます。「直後に画像が続く見出し」の下余白だけ詰める、といった微調整が効きます。

CSS
h2:has(+ figure) {
  margin-bottom: 0.5rem;
}

④ カレント表示のナビゲーション

現在地のリンクを含む項目だけ強調する、といったナビの状態表現も、JavaScriptなしで書けます。

CSS
.nav-item:has(> a.active) {
  font-weight: bold;
}

JavaScriptとの比較:なぜ :has() が嬉しいのか

同じ「チェックボックスの状態で親を変える」を、旧来のJavaScriptで書くとこうなります。

Javascript
const checkbox = document.querySelector('input[type="checkbox"]');
const parent = checkbox.closest('.form-group');

checkbox.addEventListener('change', function() {
  if (checkbox.checked) {
    parent.style.backgroundColor = 'Aquamarine';
  } else {
    parent.style.backgroundColor = '';
  }
});

イベントリスナーを張り、状態を監視し、DOMを書き換える。動きはしますが、HTML構造に依存し、状態が増えるほど管理が重くなります。

一方、:has() なら同じ挙動が次の1ルールで済みます。

CSS
.form-group:has(input[type="checkbox"]:checked) {
  background-color: Aquamarine;
}

CSSに寄せるメリットは、主に次の3つです。

  1. シンプルで直感的:
    HTMLとCSSだけで状態に基づくスタイリングが可能になるため、コードがシンプルで読みやすくなります。
  2. 保守性が向上:
    JavaScriptの依存が減ることで、CSSのみでスタイルを制御でき、コード全体の保守性が向上します。
  3. 効率的なパフォーマンス
    CSSは通常、レンダリングエンジンが最適化して処理するため、軽量であり、パフォーマンスの面でも有利です。

:has() を使うときの注意点

便利な一方で、知らないとハマるポイントが3つあります。

① :has() は「unforgiving(容赦しない)」セレクタです。 :is():where() と違い、:has() の中に無効なセレクタが1つでも混じると、ルール全体が無効化されます。複数セレクタを渡すときに古いブラウザが知らない記法が混ざると、丸ごと効かなくなるわけです。回避したいときは、中を :is() でくるむと、無効な分だけ無視されます。

とりさん
とりさん

unforgivingって、何が怖いんですか?

R
R

1つのタイポや未対応セレクタで、そのルールが全部効かなくなります。気づきにくいので、複数渡すときは :has(:is(…)) でくるむのが安全です。

② パフォーマンスに注意。 body:has(...) のように対象範囲が広い :has() は、ブラウザの再計算が重くなることがあります。できるだけ範囲の狭い要素を起点にするのが安全です。

③ 古い環境が気になるなら @supports。 2026年現在はほぼ不要ですが、エンタープライズ案件などで保険をかけたいときは、フィーチャークエリでフォールバックを分けられます。

CSS
@supports (selector(:has(*))) {
  .card:has(img) { border: 2px solid blue; }
}

まとめ::has() は「親を中身で選ぶ」発想を変える

:has() は、これまでJavaScriptで書いていた「子の状態 → 親のスタイル」を、CSSだけで表現できるようにしました。フォームの状態、カードの中身、ナビのカレント、見出しの余白まで、条件付きスタイルの多くがCSSに移せます。注意点は、unforgivingであること、範囲の広い指定は重くなりうること、の2つを押さえれば十分です。

新しいセレクタを覚えること自体がゴールではありません。大事なのは、自分のコードの中で「どこをJavaScriptからCSSに寄せられるか」を観測して、置き換える一点を見つけることです。手持ちのコードを一度その目で眺めてみると、:has() で消せるJavaScriptが、意外といくつも見つかります。

あわせて読みたい

📚 モダンCSSの全体像は モダンCSS完全ガイド──設計からデザインシステムまで でシリーズ全14記事を体系的に整理しています。


Git/Claude Code 使い込んでる人ほど、止まりがちです。

「もっと勉強しなきゃ」「もっと作らなきゃ」――Claude Code, Cursor, GitLens を試すほど、知識は増えるのに、自分が前に進んでる感覚は薄れていく。

個人開発8本、月収2,000円。Web制作17年のクリエイターが、AI と「作る」「届ける」を honest に実験している過程をメルマガで公開しています。

登録特典:「AI時代に取り残されないための構造整理シート」(PDF 12p / 5ステップ)― スキル棚卸し → AI との組み方 → 自分のポジション設計。

メルマガに登録する(無料・PDF特典付き)