第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 も書けないので、これはユーティリティクラスならではの表現力です。
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章)。
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 の薄いラッパーである、という性格がよく表れています。
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部では、これらを実際に動かすための環境構築に進みます。