CSS :has() セレクタ完全活用ガイド|親要素スタイリングの革命【2026年実践編】
CSS :has()擬似クラスの使い方を実例で解説。フォームバリデーション、条件付きレイアウト、JavaScript不要の親要素スタイリングを実現する方法。
CSS の :has() 擬似クラスは「親セレクタ」と呼ばれることもある革新的な機能です。これまで CSS では不可能だった「子要素の状態に応じて親要素をスタイリングする」ことが実現できます。2026年現在、全主要ブラウザでサポートされ、実務で活用しない手はありません。
:has() の基本
:has() は「指定した要素を含む要素」にマッチするセレクタです。
/* 画像を含む article だけ背景色を変える */
article:has(img) {
background-color: #f0f9ff;
}
/* チェックされた checkbox を含む label を強調 */
label:has(input:checked) {
font-weight: bold;
color: #0284c7;
}
従来の CSS では、親要素は子要素の状態を「知る」ことができませんでした。:has() によりこの制約が解消されます。
ブラウザ対応(2026年4月)
| ブラウザ | サポート開始 |
|---|---|
| Chrome 105 | 2022年8月 |
| Safari 15.4 | 2022年3月 |
| Edge 105 | 2022年9月 |
| Firefox 121 | 2023年12月 |
全主要ブラウザで 1年半以上前からサポートされています。もはやプログレッシブ・エンハンスメントではなく、標準の武器として使える段階です。
実践パターン7選
1. 画像を含むカードのレイアウト変更
/* 画像を含むカード → 2カラムレイアウト */
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1rem;
}
/* 画像なしカード → 通常レイアウト */
.card:not(:has(img)) {
padding: 2rem;
}
コンテンツの有無で自動的にレイアウトが切り替わります。JavaScript でクラスを付与する必要がありません。
2. フォームのリアルタイムバリデーション
/* 必須項目が未入力のフォームを強調 */
form:has(input:invalid) {
border: 2px solid #ef4444;
}
/* 全項目が valid な時だけ送信ボタンを有効化 */
form:not(:has(input:invalid)) button[type="submit"] {
background: #22c55e;
cursor: pointer;
}
HTML のバリデーション属性(required, pattern 等)と組み合わせることで、JavaScript なしで動的なフォームUIが実装できます。
3. 空のコンテナを非表示
/* 子要素がない ul を非表示 */
ul:not(:has(li)) {
display: none;
}
/* 空のセクションを非表示 */
section:empty,
section:not(:has(*)) {
display: none;
}
動的にコンテンツが追加されるUIで、「空の時は表示しない」というルールを CSS だけで実現できます。
4. 特定の子要素に応じた親の装飾
/* h1 の次が img の場合 → タイトルとヒーロー画像のレイアウト */
.hero:has(h1 + img) {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
}
/* 注目バッジを含む記事カードを強調 */
.post-card:has(.badge-featured) {
border: 2px solid #f59e0b;
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
}
5. ダークモード切り替えとの連動
body:has(#dark-mode-toggle:checked) {
background: #0f172a;
color: #e2e8f0;
}
body:has(#dark-mode-toggle:checked) a {
color: #7dd3fc;
}
チェックボックスでダークモードを実装する際、body 全体のスタイルを切り替えられます。
6. フォーカス時の親要素ハイライト
/* フォーカス中の input を含む fieldset を強調 */
fieldset:has(input:focus) {
border-color: #0284c7;
background: #f0f9ff;
}
/* フォーカス中のカードを浮き上げる */
.card:has(:focus-visible) {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
キーボードナビゲーション時のフォーカス状態が親要素に伝わるため、アクセシビリティが向上します。
7. 複数条件の組み合わせ
/* 画像 + 日付の両方を持つ記事を強調 */
article:has(img):has(time) {
border: 1px solid #e2e8f0;
padding: 1rem;
}
/* エラーと warning の両方があるフォーム */
form:has(.error):has(.warning) {
background: #fff7ed;
}
:has() は複数回チェーンできるため、複雑な条件も表現可能です。
:has() と組み合わせる便利なセレクタ
:has(+ …) で隣接要素をチェック
/* 次に pre が来る h2 のマージンを調整 */
h2:has(+ pre) {
margin-bottom: 0.5rem;
}
:has(~ …) で後続要素をチェック
/* 後続に .error がある input を強調 */
input:has(~ .error) {
border-color: #ef4444;
}
パフォーマンスへの影響
:has() は強力ですが、乱用すると再計算コストが発生します。以下に注意しましょう。
悪い例: 広範囲な :has()
/* body 全体を監視するため、どんな変更でも再計算が走る */
body:has(*) {
...
}
良い例: スコープを絞る
/* 特定のコンテナ内に限定 */
.widget:has(.widget-item.active) {
...
}
実測では、適切にスコープを絞れば :has() のパフォーマンス影響は無視できるレベルです。Chrome の DevTools の Performance パネルで “Recalculate Style” の時間を計測し、問題が起きていないか確認すると安心です。
フォールバック戦略
:has() をサポートしていないブラウザへの対応:
/* デフォルト(フォールバック) */
.card {
padding: 1rem;
}
/* :has() 対応ブラウザのみ */
@supports selector(:has(*)) {
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
}
2026年現在、ブラウザシェアの 95% 以上が :has() をサポートしているため、フォールバックなしでも多くの場面で問題ありません。
実務での活用例
本ブログ(Code & Craft)でも :has() を活用しています。
/* 目次を含む記事ページのレイアウト調整 */
.article:has(.toc) {
display: grid;
grid-template-columns: 1fr 240px;
gap: 2rem;
}
/* コードブロックを含むセクションの余白調整 */
section:has(pre) {
margin-bottom: 2rem;
}
まとめ
CSS :has() は、以下の場面で特に威力を発揮します。
- フォームの状態管理: JavaScript なしでバリデーション UI
- 条件付きレイアウト: コンテンツの有無で切り替え
- アクセシビリティ向上: フォーカス状態の親要素への伝播
- 動的UIの簡素化: クラス付与の JavaScript を削減
:has() は「CSS だけでどこまでできるか」の境界を大きく広げました。JavaScript を減らし、宣言的で保守性の高いコードが書けるようになります。ぜひ既存プロジェクトのリファクタリングに取り入れてみてください。