CSS親セレクタ :has() の超便利マークアップレシピ集
長年、CSS開発者は親セレクタ — 子要素に基づいて親要素をスタイリングする方法 — を夢見てきました。JavaScriptや余分なクラス名が唯一の回避策でした。そこに登場したのが:has()疑似クラスです。「この要素がXを含んでいたら、そのようにスタイルを変える」という処理をネイティブCSSで実現します。
2026年現在、:has()はすべての主要ブラウザでサポートされており、JavaScriptに頼ることなく、マークアップの簡略化とクリエイティブなスタイリングの世界を切り開きます。
:has() の基本
:has()疑似クラスは、引数として相対セレクタリストを受け取り、指定されたセレクタのいずれかが子孫または兄弟にマッチする場合にその要素にスタイルを適用します。
/* カード内に画像がある場合のみパディングをなくす */
.card:has(img) {
padding: 0;
}
/* 入力がフォーカスされたときラベルの色を変える */
label:has(input:focus) {
color: blue;
}
/* フォームグループ内に無効なフィールドがある場合 */
.field-group:has(:invalid) {
border-color: red;
}
レシピ1:フォームバリデーションのスタイリング
最も実用的な用途のひとつがフォーム状態のスタイリングです。各入力の:valid/:invalidをJavaScriptで追跡して親クラスをトグルする代わりに、:has()がネイティブで処理します。
.form-group:has(input:invalid) .error-message {
display: block;
}
.form-group:has(input:invalid) {
border-left: 3px solid #e74c3c;
}
.form-group:has(input:valid) {
border-left: 3px solid #2ecc71;
}
.form-group:has(input:placeholder-shown) {
border-left: 3px solid #ccc;
}
このスニペットは、エラーメッセージの表示/非表示と枠線の色を入力状態のみに基づいて切り替えます。JavaScriptは一切不要です。
レシピ2:動的なカードレイアウト
画像を含むカードとテキストのみのカードでは、パディングを変えたいことがよくあります。:has()を使えばこれが簡単に実現できます。
.card {
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card:has(img) {
padding: 0;
}
.card:has(img) .card-content {
padding: 16px 24px 24px;
}
.card:has(.badge) {
position: relative;
overflow: visible;
}
カードが画像やバッジを含むかどうかに応じて、テンプレートレベルの条件分岐なしにレイアウトが自動調整されます。
レシピ3:兄弟要素を意識した星評価
:has()は子孫コンビネータだけでなく兄弟コンビネータとも連携し、評価ウィジェットで前の星をハイライトするようなパターンを実現します。
.star-rating {
display: flex;
flex-direction: row-reverse;
}
.star-rating label {
cursor: pointer;
color: #ccc;
}
.star-rating label:has(~ label:checked),
.star-rating label:checked {
color: gold;
}
一般兄弟コンビネータ~を:has()内で使うことで、DOM上で後に現れる兄弟に基づいてスタイリングできます — これまではJavaScriptなしでは不可能だったパターンです。
レシピ4:フィルタリングとギャラリーレイアウト
JavaScriptのイベントハンドラなしで、データ属性に基づいてアイテムの表示/非表示を切り替えるフィルタリングギャラリーを作成できます。
.gallery-item {
display: block;
}
/* フィルターが有効な場合、.active のないアイテムを非表示に */
.gallery:has(.filter-active) .gallery-item:not(.active) {
display: none;
}
.gallery:has(.filter-active) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
フィルターボタンのコンテナに.filter-activeクラスを適用するだけで、ギャラリー全体のレイアウトを切り替えられます。
レシピ5:ナビゲーションとドロップダウンメニュー
従来のCSSオンリードロップダウンはホバーベースのハックが必要でした。:has()を使えば、アクセシブルで状態を認識するナビゲーションを構築できます。
.nav-item:has(.dropdown-menu) {
position: relative;
}
.nav-item:has(.dropdown-trigger:hover) .dropdown-menu,
.nav-item:has(.dropdown-trigger:focus-within) .dropdown-menu {
display: block;
}
トリガーにホバーしているときも、メニュー自体にホバーしているときもメニューを開いたままにできます。CSSだけで実現します。
レシピ6:テーブル行のハイライト
特定のセルに特定の値が含まれている場合、テーブル行全体をハイライトします。
tr:has(td.status-error) {
background: #fde8e8;
}
tr:has(td.status-success) {
background: #e8fde8;
}
tr:has(td.amount-high) td.amount {
font-weight: bold;
color: #e67e22;
}
ダッシュボードや管理パネルで特に有用で、行レベルの視覚的手がかりがスキャン性を大幅に向上させます。
パフォーマンスの考慮点
:has()は強力ですが、使いすぎや大きなDOMツリーでの使用には注意が必要です。
- 深いネストを避ける —
:has(div span p)は複雑なサブツリー検査をトリガー - シンプルなセレクタを優先 —
:has(img[src^="https"].lazy)より:has(img)を推奨 - スコープを限定 — ドキュメントルートではなくコンテナに
:has()を適用 - ブラウザ最適化 — モダンエンジンは高速ですが、大規模ページでの無数の
:has()はコストがかかる
/* 推奨 */
.list-item:has(.active) { background: blue; }
/* 非推奨 */
body :has(.active) { background: blue; }
:has()はJavaScriptの複雑さを減らすために使い、すべてのJSパターンを置き換えようとしないことが重要です。
まとめ
:has()疑似クラスは、ここ数年で最も重要なCSS追加機能のひとつです。数え切れないJavaScriptの回避策を排除し、テンプレートを簡略化し、純粋なCSSではこれまで不可能だったスタイリングパターンを実現します。
フォーム、カード、ナビゲーション、テーブル、ギャラリーで:has()を採用することで、より少ないコード量で、より少ないJavaScriptを配信し、より保守性の高いインターフェースを構築できます。ブラウザサポートが100%に達した今、:has()を使い始めない理由はありません。
