dev TailwindCSS

Tailwind CSS Complex Selectors 完全ガイド - 複雑な条件でのスタイリング方法

Tailwind CSS complex selectors(複雑なセレクタ)を実践的に学ぶ完全ガイド。バリアントの積み重ね、group-hover、data属性、任意のバリアントまで、実際のコード例とブラウザ表示で詳しく解説します。

読者がこの記事から得られる知識

この記事では、Tailwind CSS complex selectors(複雑なセレクタ)の基本概念を実践的に学びます。複数の条件を組み合わせた高度なスタイリング方法を理解できます。

この記事で学べること:

  • Tailwind CSSにおけるcomplex selectors(複雑なセレクタ)の基本概念とバリアントの積み重ね方
  • group-hoverなどのグループバリアントで親要素の状態に応じて子要素をスタイリングする方法
  • data属性を使った条件付きスタイリングの実装方法
  • 任意のバリアント構文を使った制御できないHTMLへの対応方法
  • groupクラスの役割と、親子関係でのスタイリングの仕組み

実際にコードを書いて、ブラウザで結果を確認しながら学習を進めていきます。「ダークモードで、かつ、大画面で、かつ、ホバー時に」のような複雑な条件でのスタイリング方法が身につきます。

今回ハンズオンした内容

今回は、Tailwind CSS公式ドキュメントの「Complex selectors」セクション(https://tailwindcss.com/docs/styling-with-utility-classes#complex-selectors)を参照しながら、complex selectors(複雑なセレクタ)を実践しました。CSS フレームワークであるTailwind CSSでは、複数のバリアント(条件)を組み合わせることで、非常に複雑な条件下でのスタイリングが可能です。

ファイル構造

tailwindcss/
├── src/
│   ├── index.html
│   ├── input.css
│   └── output.css
├── package.json
├── package-lock.json
└── README.md

ステップ1: 複雑なセレクタを使ったサンプルコードの追加

実行する操作:

src/index.html に、様々な複雑なセレクタを使ったサンプルコードを追加します。

  <!-- Using arbitrary values サンプル -->
  <div class="mt-8 mx-auto max-w-sm">
    <!-- 既存のコード -->
  </div>

<!-- ↓ ここから追加 --> <!-- Complex selectors サンプル --> <div class="mt-8 mx-auto max-w-sm"> <h2 class="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Complex Selectors</h2>

<!-- 1. バリアントの積み重ね --> <div class="mb-6"> <p class="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">1. Stacking Variants</p> <button class="bg-blue-500 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-600 dark:bg-blue-700 dark:hover:bg-blue-800 lg:px-8 lg:py-4 lg:text-lg" > Hover me (changes on large screens too) </button> </div>

<!-- 2. group-hover --> <div class="mb-6"> <p class="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">2. Group Hover</p> <a href="#" class="group block bg-white dark:bg-slate-800 rounded-lg p-6 shadow-lg hover:shadow-xl transition-shadow"> <div class="flex items-center gap-4"> <div class="w-12 h-12 bg-purple-500 rounded-full flex items-center justify-center text-white font-bold group-hover:bg-purple-600 transition-colors"> A </div> <div> <h3 class="font-semibold text-gray-900 dark:text-white">Article Title</h3> <p class="text-sm text-gray-500 dark:text-gray-400 group-hover:text-gray-700 dark:group-hover:text-gray-300"> Hover over the card to see changes </p> <span class="text-blue-500 text-sm font-medium group-hover:underline">Read more…</span> </div> </div> </a> </div>

<!-- 3. data属性を使った条件付きスタイル --> <div class="mb-6"> <p class="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">3. Data Attribute Styling</p> <div class="space-y-2"> <button data-state="inactive" class="w-full px-4 py-2 rounded-lg font-medium bg-gray-200 text-gray-700 data-[state=active]:bg-green-500 data-[state=active]:text-white" > Inactive Button </button> <button data-state="active" class="w-full px-4 py-2 rounded-lg font-medium bg-gray-200 text-gray-700 data-[state=active]:bg-green-500 data-[state=active]:text-white" > Active Button </button> </div> </div>

<!-- 4. 任意のバリアント --> <div class="mb-6"> <p class="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">4. Arbitrary Variant</p> <div class="[&>span]:block [&>span]:p-2 [&>span]:mb-2 [&>span]:rounded [&>span]:bg-gray-100 dark:[&>span]:bg-slate-700"> <span>First child - styled by parent's arbitrary variant</span> <span>Second child - styled by parent's arbitrary variant</span> <span>Third child - styled by parent's arbitrary variant</span> </div> </div>

<!-- 5. 複雑な組み合わせ --> <div class="mb-6"> <p class="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">5. Complex Combination</p> <div class="group bg-white dark:bg-slate-800 rounded-lg p-6"> <button data-status="available" class="px-4 py-2 rounded-lg font-medium bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-400 dark:hover:bg-gray-500 data-[status=available]:bg-green-500 data-[status=available]:text-white data-[status=available]:hover:bg-green-600 lg:px-6 lg:py-3" > Available (dark mode + hover + data + responsive) </button> </div> </div> </div> <!-- ↑ ここまで追加 -->

</body> </html>

操作の意味:

5つの異なる複雑なセレクタの使用例を追加しています:

  • バリアントの積み重ね:複数の条件(hover、dark、lg)を組み合わせ
  • group-hover:親要素のホバーで子要素すべてが変化
  • data属性:JavaScriptで管理される状態に応じたスタイリング
  • 任意のバリアント:親要素が子要素すべてに同じスタイルを適用
  • 複雑な組み合わせ:すべてのテクニックを組み合わせた例

操作を実行する意図:

Tailwind CSSでは、バリアント(条件)を連結することで、非常に複雑な条件下でのスタイリングが可能であることを実証します。特にgroupクラスを使った親子関係のスタイリングは、実務で頻繁に使われる重要なテクニックです。

実行結果:

ブラウザで表示すると、以下のように5つの例が表示されます。

Tailwind CSS complex selectors - バリアントの積み重ね、group-hover、data属性の実装例

実行結果の解説:

期待通り、すべての複雑なセレクタが正しく動作しています:

1. Stacking Variants(バリアントの積み重ね) - ホバーで色が変わり、ダークモードでも動作し、画面サイズを大きくするとボタンが大きくなります

2. Group Hover - カード全体にホバーすると、紫色のアイコン(A)が濃い紫に変わり、説明文の色が変わり、「Read more…」に下線が表示されます。これがgroupクラスの威力です

3. Data Attribute Styling - 上のボタン(Inactive)はグレーで、下のボタン(Active)は緑色です。data-state属性の値に応じてスタイルが変わっています

4. Arbitrary Variant - 3つのspanすべてが同じスタイル(背景色グレー、padding、角丸)で表示されています。親要素の[&>span]:〇〇が子要素すべてに適用されています

5. Complex Combination - 緑色のボタンが表示され、ホバーすると少し濃い緑になり、ダークモードでも動作し、大画面でボタンが大きくなります

これらすべてが、追加のCSSを書くことなく、Tailwindのユーティリティクラスだけで実現できています。

ステップ2: groupクラスの仕組みを理解する

実行する操作:

Group Hoverの例を詳しく見て、groupクラスの役割を理解します。

<a href="#" class="group block bg-white ...">
  <div class="w-12 h-12 bg-purple-500 ... group-hover:bg-purple-600">
    A
  </div>
  <p class="text-sm text-gray-500 ... group-hover:text-gray-700">
    Hover over the card to see changes
  </p>
  <span class="text-blue-500 ... group-hover:underline">Read more…</span>
</a>

操作の意味:

親要素(タグ)にgroupクラスを追加し、子要素にgroup-hover:を使っています。

操作を実行する意図:

操作を実行する意図:

groupクラスの役割を正確に理解することが重要です。よくある誤解を解消します:

  • ❌ 誤解:「groupクラスを付けたら、子要素全部に同じスタイルが適用される」
  • ✅ 正解:「groupクラスを付けたら、子要素がgroup-hover:を使えるようになる」

実行結果の解説:

groupクラスは「親の状態変化を子に通知する仕組み」です。

親要素の役割:

<a href="#" class="group ...">
  • group ← 「私がホバーされたら、子要素に知らせる」という宣言
  • 親自身にhover:スタイルがあるかどうかは全く関係ない

子要素の役割:

<div class="bg-purple-500 group-hover:bg-purple-600">
  ↑ 通常時             ↑ 親(group)がホバーされた時
</div>

各子要素で、個別にgroup-hover:〇〇を指定する必要があります。groupクラスを付けても、自動で何かが適用されるわけではありません。

生成されるCSS:

.group:hover .group-hover\:bg-purple-600 {
  background-color: var(--color-purple-600);
}

ポイントは、.group:hover(親がホバーされた時)に.group-hover\:bg-purple-600(この特定のクラスを持つ子要素)が対象になることです。

ハンズオンの中で私が疑問に感じた点や失敗した点

よくある疑問

疑問1: 親にgrouphover:shadow-xlがあるから、子ではgroup-hoverとなるのでしょうか?

解決手段:

いいえ、違います。

❌ 誤解:
「親にhover:shadow-xlがあるから、子でgroup-hover:が使える」

✅ 正解:
「親にgroupがあるだけで、子でgroup-hover:が使える」

親要素のクラスを分解すると:

<a href="#" class="group block bg-white hover:shadow-xl">
クラス役割子への影響
group子要素がgroup-hover:を使えるようにする宣言✅ これが重要!
blockこの要素自体をblock表示❌ 関係なし
bg-whiteこの要素自体の背景色❌ 関係なし
hover:shadow-xlこの要素自体がホバーされた時の影❌ 関係なし

重要:

  • groupがあれば、親にhover:が無くてもgroup-hover:は使える
  • 逆に、groupが無ければ、親にhover:があってもgroup-hover:は使えない

疑問2: 子が使えるのは、group-hoverだけですか?

解決手段:

いいえ!group-hover:だけではありません。

親にgroupクラスがあれば、子要素では多くのgroup-*バリアントが使えます:

<div class="group">
  <!-- ホバー -->
  <span class="group-hover:text-red-500">親がホバーされたら赤</span>

  <!-- フォーカス -->
  <span class="group-focus:text-blue-500">親がフォーカスされたら青</span>

  <!-- アクティブ -->
  <span class="group-active:text-green-500">親がクリックされたら緑</span>

  <!-- 無効状態 -->
  <span class="group-disabled:opacity-50">親が無効なら薄く</span>

  <!-- data属性 -->
  <span class="group-data-[state=open]:block">親にdata-state="open"があれば表示</span>
</div>

レスポンシブとの組み合わせも可能:

<div class="group">
  <span class="lg:group-hover:text-red-500">
    大画面で親がホバーされたら赤
  </span>

  <span class="dark:group-hover:text-white">
    ダークモードで親がホバーされたら白
  </span>
</div>

疑問3: バリアントの順番は重要ですか?

解決手段:

はい、バリアントの順番には推奨される順序があります。

推奨される順序:

<!-- 良い例:レスポンシブ → 状態 → ダークモード -->
<button class="lg:hover:dark:bg-blue-600">

一般的な順序:

  1. レスポンシブ(sm:md:lg:など)
  2. 状態(hover:focus:active:など)
  3. ダークモード(dark:
  4. その他(group-*data-*など)

ただし、Tailwind CSSは順序に厳密ではなく、どの順序で書いても動作します。推奨順序は可読性のためのベストプラクティスです。

<!-- これも動作する(ただし読みにくい) -->
<button class="dark:lg:hover:bg-blue-600">

<!-- これも動作する -->
<button class="hover:dark:lg:bg-blue-600">

ポイント:
チーム内で統一した順序を決めておくと、コードの可読性が向上します。

よくある失敗

失敗1: groupクラスを付け忘れる

誤った例:

<div class="bg-white p-6">  <!-- groupが無い -->
  <span class="group-hover:underline">Read more…</span>
</div>

エラーメッセージ/結果:
親要素にホバーしても、子要素に何も起こりません。group-hover:は、親にgroupクラスがある場合のみ動作します。

模範例:

<div class="group bg-white p-6">  <!-- groupを追加 -->
  <span class="group-hover:underline">Read more…</span>
</div>

ポイント:
group-*を使う場合は、必ず親要素にgroupクラスを追加します。これを忘れると、スタイルが全く適用されません。

失敗2: すべての子要素にgroup-hover:を付ければ同じスタイルになると思う

誤った例:

<div class="group">
  <span>これは赤くならない</span>
  <span>これも赤くならない</span>
</div>

エラーメッセージ/結果:
groupクラスを付けただけでは、子要素に何も起こりません。

模範例:

<div class="group">
  <span class="group-hover:text-red-500">親がホバーされたら、これだけ赤くなる</span>
  <span>これは変わらない</span>
  <span class="group-hover:text-blue-500">親がホバーされたら、これは青くなる</span>
</div>

ポイント:
groupは「子が親の状態を監視できるようにするスイッチ」です。各子要素で個別にgroup-hover:を指定する必要があります。

失敗3: data属性の構文を間違える

誤った例:

<button data-state="active" class="data-state-active:bg-green-500">
  Active
</button>

エラーメッセージ/結果:
スタイルが適用されません。data属性の構文が間違っています。

模範例:

<button data-state="active" class="data-[state=active]:bg-green-500">
  Active
</button>

ポイント:
data属性を使う場合は、data-[属性名=値]:クラス名という構文を使います。角括弧[]と等号=を正しく使うことが重要です。

失敗4: 任意のバリアントで複雑なセレクタを書きすぎる

誤った例:

<div class="[&>div>span.active+p]:text-red-500">
  <!-- 複雑すぎて読めない -->
</div>

エラーメッセージ/結果:
動作はしますが、コードが非常に読みにくく、メンテナンスが困難になります。

模範例:
HTMLの構造を変更するか、カスタムCSSを使います:

<!-- HTMLの構造を単純化 -->
<div class="[&>p]:text-red-500">
  <p>This is red</p>
</div>

<!-- またはカスタムCSSを使う -->

ポイント:
任意のバリアント[&>...]:は便利ですが、複雑すぎるセレクタは避けるべきです。HTMLの構造を見直すか、カスタムCSSを検討しましょう。

失敗5: バリアントを積み重ねすぎて可読性が低下する

誤った例:

<button class="sm:md:lg:xl:2xl:hover:focus:active:dark:group-hover:data-[state=active]:bg-blue-600">
  <!-- 長すぎて読めない -->
</button>

エラーメッセージ/結果:
動作はしますが、クラス名が長すぎて、何が適用されているのか分かりません。

模範例:
必要最小限のバリアントに絞るか、コンポーネント化します:

// Reactコンポーネントとして抽出
export function ActionButton({ state, children }) {
  return (
    <button 
      data-state={state}
      className="lg:hover:dark:data-[state=active]:bg-blue-600"
    >
      {children}
    </button>
  );
}

ポイント:
バリアントは3〜4個までに抑えることを推奨します。それ以上必要な場合は、コンポーネント化を検討しましょう。

記載内容の翻訳

公式ドキュメント(https://tailwindcss.com/docs/styling-with-utility-classes#complex-selectors)の内容を日本語で読み取れるようにします。

Complex selectors

複数の条件の組み合わせの下で要素をスタイリングする必要がある場合があります。例えば、ダークモードで、特定のブレークポイントで、ホバー時に、要素が特定のdata属性を持つ場合などです。

Tailwindでは、このような場合は以下のように記述します:

<button class="dark:lg:data-current:hover:bg-indigo-600 ...">
  <!-- ... -->
</button>

簡略化されたCSS:

@media (prefers-color-scheme: dark) and (width >= 64rem) {
  button[data-current]:hover {
    background-color: var(--color-indigo-600);
  }
}

Tailwindはgroup-hoverのような機能もサポートしており、特定の親要素がホバーされた時に要素をスタイリングできます:

<a href="#" class="group rounded-lg p-8">
  <!-- ... -->
  <span class="group-hover:underline">Read more…</span>
</a>

簡略化されたCSS:

@media (hover: hover) {
  a:hover span {
    text-decoration-line: underline;
  }
}

このgroup-*構文は他のバリアントでも機能します。例えばgroup-focusgroup-active、その他多数があります。

非常に複雑なシナリオ(特に制御できないHTMLをスタイリングする場合)では、Tailwindは任意のバリアントをサポートしており、クラス名の中に直接任意のセレクタを記述できます:

<div class="[&>[data-active]+span]:text-blue-600 ...">
  <span data-active><!-- ... --></span>
  <span>This text will be blue</span>
</div>

簡略化されたCSS:

div > [data-active] + span {
  color: var(--color-blue-600);
}

今回のまとめ

お疲れさまでした!今回は、Tailwind CSS complex selectors(複雑なセレクタ)について実践的に学習しました。

今回学習したこと:

  • Tailwind CSS complex selectors(複雑なセレクタ)により、複数のバリアント(条件)を連結して非常に複雑な条件下でのスタイリングが可能
  • groupクラスは「親の状態変化を子に通知する仕組み」で、親自身にhover:スタイルがあるかどうかは関係なく、groupがあれば子でgroup-hover:などが使える
  • data属性、任意のバリアント、レスポンシブなど、様々なテクニックを組み合わせることで、CSS フレームワークとして柔軟かつ強力なスタイリングが実現できる

次回は「When to use inline styles」セクションで、インラインスタイルを使うべき場面を学習します。また別の記事でお会いしましょう!

-dev, TailwindCSS