親要素の状態による子要素のスタイリング
問題
親のインタラクティブ状態(ホバー、フォーカス、チェックなど)に基づいて子要素をスタイリングすることは、最も一般的なUIパターンの1つです — カードをホバーするとタイトルがハイライトされたり、inputにフォーカスするとアイコンが変わったり、チェックボックスをオンにすると追加コンテンツが表示されたりします。AIエージェントはこれらのパターンにJavaScriptのイベントリスナーとクラストグルを使おうとしたり、各子要素に個別にホバースタイルを適用したりする傾向があります。Tailwind CSSは group と group-hover: ユーティリティでこれをエレガントに解決しましたが、基盤となるCSSパターンはシンプルで、多くの開発者が認識しているよりも強力です。
解決方法
CSSは親の状態に基づく子スタイリングのための3つの補完的なメカニズムを提供しています:
- 擬似クラスと子孫コンビネーター —
.parent:hover .childで親がホバーされた時に子をターゲットにします :focus-within— 要素自体またはその子孫がフォーカスを持つ時にマッチします:has()— 最も強力:任意の子の状態に基づいて親(およびその子)をスタイリングします
これらを組み合わせることで、Tailwindの group-* ユーティリティが処理するすべてのシナリオ、さらにそれ以上をカバーします。
コード例
基本:親のホバー → 子のスタイリング
最もシンプルなパターンです。親がホバーされると子が応答します。
.card:hover .card__title {
color: blue;
}
.card:hover .card__icon {
transform: translateX(4px);
}
.card:hover .card__arrow {
opacity: 1;
}
これはTailwindの group / group-hover: がコンパイルされる先のCSSです。親が「グループ」で、子がその状態に反応します。
Focus-Within:キーボードアクセシブルなグループフォーカス
:focus-within は要素自体または任意の子孫がフォーカスを持つ時にマッチします。キーボードアクセシビリティに不可欠で、:hover が親に対して提供するのと同じ連動スタイリングをフォーカスイベントに対して実現します。
/* The search bar container highlights when its input is focused */
.search-bar:focus-within {
border-color: hsl(220 70% 50%);
box-shadow: 0 0 0 3px hsl(220 70% 50% / 0.15);
}
.search-bar:focus-within .search-bar__icon {
color: hsl(220 70% 50%);
}
.search-bar:focus-within .search-bar__label {
transform: translateY(-100%) scale(0.85);
color: hsl(220 70% 50%);
}
:has() — 最も強力なグループパターン
:has() はホバーやフォーカスを超えた能力を持ちます。任意の子の状態(チェックされたチェックボックス、入力されたinput、選択されたオプション、さらには構造的条件)に基づいて親(およびその子)をスタイリングできます。
/* Highlight the form group when its checkbox is checked */
.option-group:has(input:checked) {
background: hsl(220 60% 97%);
border-color: hsl(220 70% 50%);
}
.option-group:has(input:checked) .option-group__label {
color: hsl(220 70% 40%);
font-weight: 600;
}
/* Reveal extra content when checked */
.option-group:has(input:checked) .option-group__details {
display: block;
}
ホバーと Focus-Within の組み合わせ
堅牢なインタラクティブコンポーネントには、マウスとキーボードの両方のユーザーで動作するよう :hover と :focus-within の両方を組み合わせましょう。
.nav-item:hover .nav-item__tooltip,
.nav-item:focus-within .nav-item__tooltip {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
:has(:checked) による表示切り替え
クラシックなパターンです。チェックボックスやラジオの状態に基づいてコンテンツセクションの表示/非表示を切り替えます。JavaScriptは不要です。
ネストされたグループ:複数の祖先レベル
異なる祖先レベルが異なる子スタイルを駆動する必要がある場合、各レベルに個別のクラス名を使用しましょう。
/* Outer group: the card */
.card:hover .card__badge {
background: hsl(220 70% 50%);
}
/* Inner group: the card footer */
.card__footer:hover .card__footer-link {
text-decoration: underline;
}
これはTailwindの名前付きグループ(group/card、group/footer)のCSS等価物です。各祖先の状態がそれぞれの子孫を独立して制御します。
Tailwind group → CSS マッピング
| Tailwind ユーティリティ | CSS 等価物 |
|---|---|
group + group-hover:text-blue | .parent:hover .child { color: blue; } |
group + group-focus:opacity-100 | .parent:focus .child { opacity: 1; } |
group + group-focus-within:ring-2 | .parent:focus-within .child { ... } |
group + group-active:scale-95 | .parent:active .child { transform: scale(0.95); } |
group/name(名前付きグループ) | 祖先レベルごとに個別のクラス名を使用 |
group-has-[:checked]:bg-blue | .parent:has(:checked) { background: blue; } |
AIがよくやるミス
- ホバーエフェクトにJavaScriptでクラスをトグルする —
.parent:hover .childを使いましょう。イベントリスナーは不要です。 - すべての子要素にホバースタイルを重複して書く — 親に一度
:hoverを適用し、そこからすべての子をスタイリングしましょう。 - キーボードアクセシビリティを忘れる — インタラクティブコンテナには常に
:hoverと:focus-withinをペアにしましょう。ホバーのみのパターンはキーボードユーザーを排除します。 :has()を過度に使用する — シンプルな.parent:hover .childで十分な場合に:has()を使わないでください。:has()は子の内部状態(checked、valid、empty)に反応する必要がある場合に使いましょう。
使い分け
- カードのホバーエフェクト:タイトル、アイコン、背景の連動変化に使います。
- フォームグループ:inputがフォーカスされた時や無効な時にグループ全体をハイライトする場合に使います。
- ナビゲーションメニュー:ホバー/フォーカス時にツールチップやドロップダウンを表示する場合に使います。
- オプションセレクター:ラジオ/チェックボックスの状態に基づいて選択されたオプションをスタイリングする場合に使います。
- アコーディオン/ディスクロージャー:
:has(:checked)で表示を切り替える場合に使います。 - Tailwindが
groupを使用するパターンすべて — CSSは常に子孫コンビネーター + 擬似クラスです。