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-700hover:md:flexmd: のように、ユーティリティの前に付けて「どんな条件のときに効かせるか」を指定する接頭辞——これがバリアントです。第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); }
.hover\:bg-blue-700:hover { background-color: var(--color-blue-700); }

hover: というバリアントは、要するに CSS の :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: … マウスが乗っているとき(:hover
  • focus: … フォーカスが当たっているとき(:focus
  • focus-visible: … キーボード操作などでフォーカスが当たったとき。マウスクリック時には出さない、アクセシビリティに配慮したフォーカス表示に使います(第21章
  • active: … 押されている最中(:active
  • disabled: … 無効化されているとき(:disabled

いずれも、対応する CSS の擬似クラスに変換されているだけです。

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) です。生成されるメディアクエリは @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-checkedaria-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) {
    .dark\:md\:hover\:bg-fuchsia-600:hover {
      background-color: var(--color-fuchsia-600);
    }
  }
}

複雑に見えますが、左から順に条件を絞り込んでいるだけです。なお、書く順番は読みやすさのために「環境 → 状態」の順(dark:md:hover:)に揃えるチーム規約にしておくと、後から読みやすくなります。

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

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

@custom-variant pointer-coarse (@media (pointer: coarse));

これで pointer-coarse:p-6(指で操作するタッチデバイスのとき余白を広げる)のように使えます。

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

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

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

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

参考資料