本文へスキップ
T2R tech2rich.com
Web開発 🔤 CSS 📚 モダンCSS入門 第1回 #CSS #モダンCSS #セレクタ #フロントエンド

【モダンCSS入門 第1回】CSS `:has()` 擬似クラス完全入門 — 親要素を選択する5つの実用パターン

長年待ち望まれた「親要素を選択できる CSS セレクタ」がついに実用化。:has() の基本構文から、カード・フォーム・ナビでの5つの実用パターンまで、JavaScript なしで実現するコード例で解説します。

📅 公開: 2026.05.19 ⏱ 読了 約7分 ✍ 管理人

「子要素の状態に応じて、親要素のスタイルを変えたい」

Web開発をしていると、何度もぶつかるこの要望。これまで CSS だけでは不可能で、JavaScript でクラスを付け外しするのが定番でした。

それが CSS :has() 擬似クラスの登場で、ついに CSS 単独で「親要素の選択」が可能になりました。すべての主要ブラウザでサポートされ、本番投入できるレベルに到達しています。

この記事では、:has() の基本構文から、現場ですぐ使える 5つの実用パターン までを、コード例つきで解説します。

先輩

この記事のゴール

:has() の構文を理解し、これまで JavaScript で書いていた処理を CSS に置き換える具体的な引き出しを5つ手に入れることです。

:has() 擬似クラスとは?

:has()「特定の子要素・子孫要素を持つ親要素」 を選択するための CSS 擬似クラスです。

/* 「画像を含む article」だけにスタイルを当てる */
article:has(img) {
  padding: 24px;
  background: #f8fafc;
}

この記述は img を子孫に持つ article という意味になります。article 自体が選択対象であり、img ではない点がポイントです。

なぜ「親セレクタ」と呼ばれるのか

従来の CSS セレクタは 「左から右」 にしか辿れませんでした。

しかし :has() を使うと、子から親をさかのぼって選択できます。これが「親セレクタ」と呼ばれる所以です。

基本構文

:has() の引数には、通常の CSS セレクタを渡せます。

/* 直下の子に img があるカード */
.card:has(> img) { ... }

/* チェックされたチェックボックスを含む label */
label:has(input:checked) { ... }

/* エラー状態の input を持つフォーム */
form:has(input:invalid) { ... }

/* img も video も持たない article */
article:not(:has(img, video)) { ... }

セレクタリスト(カンマ区切り)も渡せるので、「画像か動画のいずれかを持つ」 といった条件も書けます。

パターン1: 画像を含むカードだけレイアウトを変える

ECサイトや記事一覧でよくある要望です。

/* デフォルトのカード */
.card {
  display: block;
  padding: 16px;
}

/* 画像を含むカードは横並びに */
.card:has(img) {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 16px;
}

これまでなら HTML 側で .card--with-image のようなクラスを付ける必要がありました。:has() なら HTML はそのままで、CSS だけで条件分岐できます。

パターン2: チェック状態で親要素を変化させる

「選択中のオプションだけ強調する」UI を JavaScript なしで実現できます。

<label class="option">
  <input type="checkbox" />
  <span>プレミアムプラン</span>
</label>
.option {
  border: 2px solid #e5e7eb;
  padding: 12px 16px;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s;
}

/* チェックされたら label 全体を強調 */
.option:has(input:checked) {
  border-color: #0ea5e9;
  background: #f0f9ff;
  color: #0c4a6e;
}

ラジオボタンやチェックボックスの 「選択中のスタイル」 を、JavaScript なしでスマートに表現できます。

パターン3: 子要素のエラー状態をフォームに伝える

フォーム内のどこかにエラーがあったら、フォーム全体のヘッダーを赤くするといった処理も簡単です。

form {
  border: 2px solid #e5e7eb;
  padding: 24px;
  border-radius: 8px;
}

/* 中に invalid な入力があれば赤枠に */
form:has(input:invalid, textarea:invalid) {
  border-color: #ef4444;
}

/* さらに、エラー時だけ表示する警告メッセージ */
.form-error-banner {
  display: none;
}
form:has(:invalid) .form-error-banner {
  display: block;
}

:has() と他のセレクタを 組み合わせることで、「親に条件があるとき、別の子孫を表示する」といった複雑な要件もスッキリ書けます。

パターン4: 子要素の数でレイアウトを切り替える

CSS 単独で 「子要素が3つ以上なら段組み、それ未満なら1列」 のような分岐ができます。

.gallery {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* 子要素を3つ以上持つときだけグリッド表示 */
.gallery:has(> :nth-child(3)) {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

:nth-child(3) という子セレクタを :has() の中で使うことで、「3番目の子が存在する=子が3個以上」 という条件を表現しています。

パターン5: 現在地に応じて親メニューをハイライト

ナビゲーションの 「現在のページを含むカテゴリ」 をハイライトしたいときに便利です。

<li class="menu-group">
  <span>製品</span>
  <ul>
    <li><a href="/product/a/">プロダクトA</a></li>
    <li><a href="/product/b/" aria-current="page">プロダクトB</a></li>
  </ul>
</li>
/* aria-current を持つリンクを含むメニューグループ */
.menu-group:has(a[aria-current="page"]) {
  background: #f0f9ff;
  font-weight: 600;
}

これまでサーバー側で「現在地クラス」を出力していた処理が、HTML の aria 属性だけで完結します。アクセシビリティと装飾を同時に解決できる、エレガントな書き方です。

ブラウザ対応について

:has() は 2023年12月にすべての主要ブラウザ(Chrome / Edge / Safari / Firefox)で安定サポートに到達し、Baseline 2023 として認定されています。

2026年現在、ほぼすべてのユーザーが利用可能と考えてよいでしょう。心配な場合は、機能検出で安全にフォールバックできます。

/* :has() をサポートする環境でだけ適用 */
@supports selector(:has(*)) {
  .card:has(img) {
    display: grid;
    grid-template-columns: 120px 1fr;
  }
}

つまずきやすいポイント

注意

⚠ ここは要注意

:has() はネスト不可です。:has(:has(...)) のように入れ子で使うと無効になります。

まとめ

CSS :has() 擬似クラスを使うと、以下が CSS 単独で実現できます。

これまで JavaScript で className を付け外しする」 ことで実現していた多くの処理が、:has()宣言的・状態同期型に書き直せます。コード量が減り、状態と表示の同期バグも起きにくくなる嬉しい変化です。

ぜひ今日から実プロジェクトに取り入れてみてください。

💡 次のステップ: :has() と相性が良い CSS の新機能には コンテナクエリ(@container):is() / :where() があります。これらを組み合わせると、より柔軟で読みやすいスタイル設計が可能になります。

関連の技術記事