CSS :has() セレクター実践:親セレクターがコンポーネントスタイルアーキテクチャを変える
前端工程(更新: 2026年6月9日)
:has() セレクター:CSS の「親セレクター」
長らく CSS は親から子へしか選択できませんでした。:has() はこの制限を打破し、子から親への逆方向選択を実現します:
/* 従来:子要素しか選択できない */
.card .title { color: blue; }
/* :has():子要素の状態に基づいて親要素を選択 */
.card:has(.title) { border-color: blue; }
ブラウザ対応
| ブラウザ | 対応バージョン | リリース |
|---|---|---|
| Chrome | 105+ | 2022.08 |
| Safari | 15.4+ | 2022.03 |
| Firefox | 121+ | 2023.12 |
| Edge | 105+ | 2022.08 |
2026 年時点で、すべての主要ブラウザが
:has()に対応しています。
構文とマッチングロジック
基本構文
/* 指定の子孫を含む */
parent:has(descendant) { }
/* 直接の子を含む */
parent:has(> child) { }
/* 兄弟要素が存在 */
element:has(+ sibling) { }
element:has(~ sibling) { }
/* 複数条件の組み合わせ */
element:has(.a, .b) { } /* OR */
element:has(.a):has(.b) { } /* AND */
:not() との組み合わせ
/* .active 子要素を含まない .nav */
.nav:not(:has(.active)) {
opacity: 0.5;
}
/* 未チェックの checkbox を含む */
.form:has(input:not(:checked)) {
border-color: orange;
}
実践例 1:フォームバリデーション状態
従来の JS アプローチ
// 各入力のバリデーション状態をリスニング
input.addEventListener('invalid', () => {
form.classList.add('has-error');
});
input.addEventListener('valid', () => {
form.classList.remove('has-error');
});
:has() アプローチ
/* 無効な入力を含むフォーム:全体スタイル変化 */
form:has(:user-invalid) {
border-color: #ef4444;
}
form:has(:user-invalid) .submit-btn {
opacity: 0.5;
pointer-events: none;
}
/* 個別の無効フィールド:コンテナをハイライト */
.field:has(:user-invalid) {
background: #fef2f2;
}
.field:has(:user-invalid) .error-msg {
display: block;
}
/* 全フィールド有効:成功メッセージ表示 */
form:has(.field:not(:has(:user-invalid))) .success-msg {
display: block;
}
<form>
<div class="field">
<label>メール</label>
<input type="email" required />
<span class="error-msg">有効なメールアドレスを入力してください</span>
</div>
<div class="field">
<label>パスワード</label>
<input type="password" required minlength="8" />
<span class="error-msg">パスワードは8文字以上</span>
</div>
<button class="submit-btn">送信</button>
<span class="success-msg">フォームは有効です</span>
</form>
実践例 2:カードレイアウト
画像あり・なしのカード自适应
/* 画像ありカード:水平レイアウト */
.card:has(img) {
display: flex;
gap: 1rem;
}
.card:has(img) .card-body {
flex: 1;
}
/* 画像なしカード:垂直レイアウト */
.card:not(:has(img)) {
display: block;
text-align: center;
}
/* 横長画像:比率調整 */
.card:has(img[width="1200"]) {
flex-direction: column;
}
カードホバー効果
/* カードのどこにホバーしても画像が拡大 */
.card:has(.card-link:hover) img {
transform: scale(1.05);
}
.card:has(.card-link:hover) .card-title {
color: var(--primary);
}
実践例 3:ナビハイライト
従来:サーバー側で active クラスを付与
<nav>
<a href="/" class="active">ホーム</a>
<a href="/blog">ブログ</a>
</nav>
:has() アプローチ:純 CSS で現在ページを感知
/* aria-current 属性を利用 */
nav:has([aria-current="page"]) {
border-bottom-color: var(--primary);
}
nav a:has(+ [aria-current="page"]),
nav a[aria-current="page"] {
color: var(--primary);
font-weight: 600;
}
/* アクティブ項目がある時のみ戻るボタン表示 */
nav:not(:has([aria-current="page"])) .back-btn {
display: none;
}
サイドバ折りたたみ感知
/* 展開されたサブメニューがある時:区切り線表示 */
.sidebar:has(.submenu:not([hidden])) {
border-right: 2px solid var(--border);
}
/* 全サブメニューが折りたたまれている時:コンパクトモード */
.sidebar:not(:has(.submenu:not([hidden]))) {
width: 64px;
}
実践例 4:ダークモード連動
画像の明るさに応じて自動調整
/* 高輝度画像を含む時:周囲のテキストを暗く */
figure:has(img.light) {
background: #1a1a1a;
color: #fff;
}
/* ダークモードで透過ロゴに背景を追加 */
@media (prefers-color-scheme: dark) {
.container:has(img[alt*="logo"]) {
background: #fff;
padding: 8px;
border-radius: 8px;
}
}
実践例 5:テーブルとリストのインタラクション
テーブル全選感知
/* 全チェックボックス選択時:ヘッダースタイル変化 */
table:has(tbody input:checked:not(:indeterminate):not(:only-child))
th {
background: var(--primary-light);
}
/* 部分選択時:一括操作バー表示 */
table:has(tbody input:checked) .batch-actions {
display: flex;
}
/* 未選択時:非表示 */
table:not(:has(tbody input:checked)) .batch-actions {
display: none;
}
リスト空状態
/* 空リストにプレースホルダー表示 */
.list:has(> :only-child.empty-placeholder) {
justify-content: center;
min-height: 200px;
}
:has() と JavaScript の使い分け
| シナリオ | :has() の利点 | JS の利点 |
|---|---|---|
| DOM 構造変化 | 自動追従、リスナー不要 | 非同期データに対応 |
| 性能 | ネイティブ、スタイル計算が速い | 計算タイミングの精密制御 |
| 複雑ロジック | 単純条件判定 | 多段計算、API 呼び出し |
| 互換性 | 2023 年以降フルサポート | 互換性の懸念なし |
性能上の注意
/* 悪い例:深いネストの :has() は性能に影響 */
.container:has(.deep1:has(.deep2:has(.deep3))) { }
/* 良い例:フラットな :has() 条件 */
.container:has(.deep3) { }
多段ネストの
:has()は避けてください。ブラウザは DOM ツリーを走査してマッチングするため、ネストが深いほどオーバーヘッドが増大します。
他の CSS4 機能との組み合わせ
:is() と :where() でセレクターを簡略化
/* :is() で多型チェックを簡略化 */
.card:has(:is(img, video, svg)) {
aspect-ratio: 16/9;
}
/* :where() で詳細度を下げ、オーバーライドしやすく */
.card:where(:has(.featured)) {
border-color: gold;
}
:modal と :popover
/* 開いたモーダルがある時:背景スクロール禁止 */
body:has(:modal) {
overflow: hidden;
}
body:has([popover]:not(:popover-closed)) {
overflow: hidden;
}
ベストプラクティス
- JS のクラス切り替えより :has() を優先:JS コード削減、スタイルロジックの集約
- :has() 条件はフラットに:ネストで性能劣化を回避
- 意味的属性と組み合わせ:
aria-current、:user-invalid、:checkedなど - プログレッシブエンハンスメント:コア機能は :has() に依存しない
- DevTools でデバッグ:Chrome DevTools は :has() セレクターのハイライトに対応
まとめ
:has() セレクターは CSS 史上最も重要な機能拡張の一つです。子から親への逆方向選択を可能にし、フォームバリデーション、条件スタイル、状態連動などで JavaScript を大幅に削減できます。:has() のマッチングロジックと性能境界を理解することは、モダンフロントエンドエンジニアの必須スキルです。
Flexbox ツール でレイアウト組み合わせを練習、Box Shadow ジェネレーター でカードシャドウ効果を作成、Border Radius ツール でカード角丸をデザインできます。
ブラウザローカルツールを無料で試す →
#:has()#CSS选择器#父选择器#条件样式#CSS4