Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第6章 バリアントの仕組み

ここまでで、ユーティリティが生成される仕組み(第4章)と、その値を決めるテーマ(第5章)を見てきました。残るピースがバリアントです。hover:bg-blue-700 の hover:、md:flex の md: のように、ユーティリティの前に付けて「どんな条件のときに効かせるか」を指定する接頭辞——これがバリアントです。第3章で「Tailwind がインラインスタイルと違う最大の理由」として挙げた、状態とレスポンシブを支える仕組みです。

6.1 バリアントとは

バリアントは、ユーティリティに「条件」を付けるものです。

<button class="bg-blue-500 hover:bg-blue-700">送信</button>

ここで bg-blue-500 は常に効く背景色、hover:bg-blue-700 はマウスを乗せたときだけ効く背景色です。生成される CSS を見ると、バリアントの正体がよく分かります(簡略化)。

.bg-blue-500 { background-color: var(--color-blue-500); }
@media (hover: hover) {
  .hover\:bg-blue-700:hover { background-color: var(--color-blue-700); }
}

hover: というバリアントは、要するに ホバー可能な主入力装置というメディア条件と、:hover セレクタを組み合わせる指示だったわけです。バリアントとは「ユーティリティを、特定のセレクタやメディアクエリでくるむためのルール」だと理解してください。インラインスタイル(style 属性)では :hover も @media も書けないので、これはユーティリティクラスならではの表現力です。

hover:bg-* と md:flex がそれぞれホバー可能な入力装置の条件付き :hover と画面幅の @media に変換される図
図 6-2 バリアントは、条件を CSS のセレクタやメディアクエリに変えるための記法。

6.2 状態系のバリアント

ユーザーの操作や要素の状態に応じたバリアントです。よく使うものを挙げます。

<button class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 focus:ring-2 disabled:opacity-50">
  送信
</button>
  • hover: … ホバー可能な主入力装置があり、要素にポインターが乗っているとき。タッチ端末のタップで同じ挙動になるとは限りません
  • focus: … フォーカスが当たっているとき(:focus)
  • focus-visible: … ブラウザがフォーカス表示を必要と判断したとき。キーボード操作が典型例ですが、テキスト入力などではマウス操作でも表示されます(第21章)。
  • active: … 押されている最中(:active)
  • disabled: … 無効化されているとき(:disabled)

バリアントは、対応する擬似クラスやメディア条件に変換されます。

6.3 構造系のバリアント

要素の位置や状態(何番目か、空かどうか)に応じたバリアントです。リストやテーブルで活躍します。

<ul>
  <li class="border-b last:border-b-0 odd:bg-gray-50">項目</li>
</ul>
  • first: / last: … 最初 / 最後の子要素(:first-child / :last-child)
  • odd: / even: … 奇数番目 / 偶数番目(:nth-child(odd) など)。表の縞模様(ゼブラ)に便利
  • only: … 子要素が 1 つだけのとき
  • empty: … 中身が空のとき

6.4 グループ・ピア(group-* / peer-*)

ここからが、バリアントの真価が出るところです。「ある要素の状態に応じて、別の要素のスタイルを変えたい」という、素の CSS では少し書きづらいことを、宣言的に書けます。

group-*(親の状態に子が反応する)

親に group を付け、子に group-hover: などを付けると、親がホバーされたときに子のスタイルが変わります。カード全体にマウスを乗せたら、中のタイトルの色を変える、といった定番の表現です。

<a href="#" class="group block p-4">
  <h3 class="text-gray-900 group-hover:text-blue-600">プロジェクト</h3>
  <p class="text-gray-500 group-hover:text-gray-700">説明文</p>
</a>

peer-*(兄弟の状態に反応する)

peer を付けた要素の状態に、同じ階層の別の要素が反応します。フォームで「入力が不正なときにエラーメッセージを出す」といった用途が代表的です。

<input type="email" class="peer border" />
<p class="invisible peer-invalid:visible text-red-600">
  有効なメールアドレスを入力してください
</p>

入力欄(peer)が :invalid のとき、後ろのメッセージが表示されます。JavaScript を書かずに、状態と見た目を連動させられるのがポイントです(フォームは第20章)。

group は親の状態が子に伝わり、peer は兄弟の状態が別要素に伝わることを示す図
図 6-1 `group-*` / `peer-*` の作用。親の状態が子に、兄弟の状態が別要素に伝わる。

6.5 メディア系のバリアント

画面や環境の条件に応じたバリアントです。中身は CSS のメディアクエリです。

  • ブレークポイント(sm: md: lg: xl: 2xl:)… 画面幅に応じた切り替え(第17章で詳述)
  • dark: … ダークモードのとき(第18章)
  • motion-reduce: … ユーザーが「視差効果を減らす」設定にしているとき。過剰なアニメーションを抑えるために使います(第19章・第21章)
  • print: … 印刷時
<div class="text-base md:text-lg dark:text-gray-200 print:text-black">...</div>

v4 のブレークポイントは、デフォルトで sm=40rem(640px)、md=48rem(768px)、lg=64rem(1024px)、xl=80rem(1280px)、2xl=96rem(1536px) です。この px 換算はブラウザの初期文字サイズ16px を前提とします。メディアクエリ内の rem はページのルート文字サイズの上書きには連動しません。生成されるメディアクエリは @media (width >= 48rem) のような新しい範囲記法になっています。

6.6 属性・データ系のバリアント

HTML の属性に応じてスタイルを当てるバリアントです。アクセシビリティ属性や、自前の状態管理と相性が良いものです。

<button aria-pressed="true" class="aria-pressed:bg-blue-600">トグル</button>
<div data-size="large" class="data-[size=large]:p-8">...</div>
  • aria-*: … aria-checked や aria-pressed などの ARIA 属性に反応。状態を見た目に反映できます(第21章)
  • data-*: … data- 属性に反応。data-[size=large]:p-8 のように属性の値で分岐もできます。JavaScript フレームワークが要素に付ける状態(開いている/選択中など)と組み合わせると強力です

v4 では、data-* バリアントが標準でかなり柔軟に書けるようになりました。Stimulus(Rails)や各種 UI ライブラリが付与する data- 属性をそのままスタイリングのフックにできます。

6.7 has-* ・ not-* ・ *

比較的新しい CSS 機能に対応したバリアントです。

  • has-*(親が特定の子を持つとき)… CSS の :has() に対応。「中にチェック済みのラジオがあるラベルの背景を変える」など、これまで JavaScript が必要だった表現を CSS だけで書けます。
<label class="has-checked:bg-indigo-50 p-4 border rounded">
  <input type="radio" name="plan" /> プラン A
</label>
  • not-*(v4)… :not() に対応。「ホバーされていないとき」のような否定条件を書けます。
  • *(直接の子すべて)… 直接の子要素すべてに同じスタイルを当てます。*:(直接の子)や **:(すべての子孫)といった形があります。

これらは「素の CSS の進化を、ユーティリティのまま使える」ようにしたものです。Tailwind がモダン CSS の薄いラッパーである、という性格がよく表れています。

dark:md:hover: が外側から内側へ条件を絞る様子を示す図
図 6-3 バリアントのスタック。`dark:md:hover:` は外から内へ条件を重ねる。

6.8 バリアントのスタック(評価順)

バリアントは重ねがけ(スタック)できます。dark: かつ md: 以上かつ hover: のとき、というように条件を組み合わせられます。

<button class="dark:md:hover:bg-fuchsia-600">保存</button>

これは「ダークモードで、画面が md 以上で、ホバーされているとき」に背景色を変える、という意味です。生成される CSS は、メディアクエリの入れ子と擬似クラスの組み合わせになります(簡略化)。

@media (prefers-color-scheme: dark) {
  @media (width >= 48rem) {
    @media (hover: hover) {
      .dark\:md\:hover\:bg-fuchsia-600:hover {
        background-color: var(--color-fuchsia-600);
      }
    }
  }
}

v4 はバリアントを左から右へ合成します。環境条件だけなら並べ替えても同じ結果になる例がありますが、順序が意味を変える場合もあります。たとえば *:first:pt-0 は「直接の子のうち最初の子」の上 padding を消し、first:*:pt-0 は「その要素自身が最初の子である場合、その直接の子すべて」の上 padding を消します。読みやすさだけで順番を変えず、対象となる要素を確認しましょう。

6.9 カスタムバリアント(@custom-variant)

用意されたバリアントで足りないときは、自分でバリアントを定義できます。v4 では @custom-variant ディレクティブを使います。

@custom-variant theme-midnight (&:where([data-theme="midnight"], [data-theme="midnight"] *));

これで <html data-theme="midnight"> の配下などで theme-midnight:bg-slate-900 が効きます。なお、タッチ操作向けの pointer-coarse:p-6 は v4.1 以降の標準バリアントなので独自定義は不要です。

実は、v4 のダークモードを「手動切り替え(クラス方式)」にする設定も、このカスタムバリアントで書きます。

@custom-variant dark (&:where(.dark, .dark *));

こう書くと、dark: は「OS 設定」ではなく「.dark クラスが自身または祖先に付いているとき」に効くようになります(第18章で詳しく扱います)。

ここまでで、Tailwind の三本柱——ユーティリティの生成(第4章)・テーマ(第5章)・バリアント(第6章)——がそろいました。この 3 つが分かれば、Tailwind の挙動はもう「魔法」ではありません。次の第3部では、これらを実際に動かすための環境構築に進みます。

参考資料