ウェブエンジニア問題集

状態修飾子 — hover・focus・active・disabled・group・peer

ボタンにマウスを乗せたら色が変わる、入力欄を選択したら枠が光る——こうした「状態に応じた見た目の変化」を、Tailwindでは状態修飾子(state modifiers) で表現します。

CSSでいう :hover:focus などの擬似クラスを、hover: focus: のようなプレフィックスとしてクラス名の先頭に付けるだけで使えます。

学習者学習者

ホバーで色を変えたいだけなのに、わざわざCSSファイルに :hover を書くのは面倒だなあ…。

先生先生

Tailwindなら hover:bg-blue-700 のようにクラスを足すだけ。CSSファイルを開かずにHTML(JSX)の中で完結するよ。

ホバーやフォーカスのインタラクションが完成したときの安心感

状態修飾子の仕組み — 修飾子:ユーティリティ

状態修飾子は 修飾子:ユーティリティ という形で書きます。コロン(:)の前が「いつ適用するか」、後ろが「何を適用するか」です。

<!--  hover:    bg-blue-700                          -->
<!--  ↑いつ     ↑何を(ホバー中だけ背景を blue-700 に) -->
<button class="bg-blue-600 hover:bg-blue-700">ボタン</button>

これはビルド時に、次のようなCSSへ変換されます。

.bg-blue-600 {
  background-color: #2563eb;
}
.hover\:bg-blue-700:hover {
  background-color: #1d4ed8;
}

つまり Tailwind が裏側で :hover 付きのCSSを生成してくれているわけです。自分でセレクタを書く必要はありません。同じ要領で、focus: active: disabled: などあらゆる状態に対応できます。


hover: — マウスを乗せたとき

最もよく使う修飾子です。色・影・下線・拡大など、ホバー時の変化に使います。

<!-- ホバーで背景色を変える -->
<button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
  ボタン
</button>
 
<!-- ホバーで下線 -->
<a class="text-blue-600 hover:underline">リンク</a>
 
<!-- ホバーで影を強調 -->
<div class="shadow hover:shadow-lg transition-shadow">カード</div>

focus: / focus-visible: — フォーカスされたとき

入力欄やボタンが選択された状態です。フォームのアクセシビリティに必須の修飾子です。

<!-- フォーカス時にリングを表示 -->
<input
  class="border border-gray-300 rounded px-3 py-2
         focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
 
<!-- キーボード操作時のみリングを表示 -->
<button class="focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none">
  ボタン
</button>

active: — クリックしている瞬間

押している間だけ適用されます。scale と組み合わせると「押し込んだ」感触を出せます。

<button class="bg-blue-600 active:scale-95 transition-transform">
  クリックで縮む
</button>

disabled: — 無効状態

disabled 属性が付いた要素に適用されます。操作不可であることを見た目で伝えます。

<button
  class="bg-blue-600 text-white px-4 py-2 rounded
         disabled:opacity-50 disabled:cursor-not-allowed"
  disabled
>
  送信
</button>

first: / last: / odd: / even: — 構造に応じたスタイル

リストやテーブルの行に対して、「最初」「最後」「奇数番目」「偶数番目」だけスタイルを変えられます。ストライプ(縞模様)テーブルなどでよく使います。

<ul>
  <!-- 偶数行だけ背景をグレーに(ゼブラストライプ) -->
  <li class="px-4 py-2 even:bg-gray-100">行1</li>
  <li class="px-4 py-2 even:bg-gray-100">行2</li>
  <li class="px-4 py-2 even:bg-gray-100">行3</li>
</ul>
 
<!-- 最後の行だけ下線を消す -->
<li class="border-b last:border-b-0">項目</li>

group / group-hover: — 親要素のホバーに連動

学習者学習者

カード全体にマウスを乗せたら、中のタイトルの色も一緒に変えたい…。これってJavaScriptが必要?

先生先生

いや、group を使えばCSSだけでできるよ。親に group、子に group-hover: を付けるだけ。

親要素にマウスを乗せたとき、子要素のスタイルを変えたいケースです。親に group を付け、子で group-hover: を使います。カードのホバーエフェクトの定番です。

<div class="group border rounded p-4 hover:bg-blue-50 transition-colors">
  <h3 class="font-semibold group-hover:text-blue-600 transition-colors">
    タイトル
  </h3>
  <p class="text-gray-500 group-hover:text-gray-700">
    親カードにホバーすると、この文字色も一緒に変わる
  </p>
</div>

group-hover: 以外にも group-focus: group-disabled: など、group-* の形で各状態に対応できます。


peer / peer-checked: — 兄弟要素の状態に連動

兄弟要素の状態に応じてスタイルを変えます。peer を付けた要素より後ろにある兄弟要素で peer-* が使えます。カスタムチェックボックスやトグルスイッチの定番パターンです。

<label class="flex items-center gap-2 cursor-pointer">
  <input type="checkbox" class="peer sr-only" />
  <!-- チェックされたらトグルの背景を青に -->
  <div class="w-10 h-6 bg-gray-300 rounded-full peer-checked:bg-blue-600 transition-colors"></div>
  <span>通知を受け取る</span>
</label>

focus-within: — 子要素がフォーカスされたとき

要素の内側のどこかがフォーカスされたら、その要素自身のスタイルを変えます。入力欄を含むフォーム枠全体を強調するときに便利です。

<div class="border rounded p-2 focus-within:ring-2 focus-within:ring-blue-500">
  <input class="outline-none" placeholder="入力するとこの枠が光る" />
</div>

aria-* / data-* 修飾子 — 属性に応じたスタイル

モダンなTailwindでは、aria-* 属性や data-* 属性の値に応じてスタイルを切り替えられます。アクセシビリティ対応や、状態を data-* で管理するUIライブラリ(Radix UIなど)との相性が良い機能です。

<!-- aria-expanded="true" のときだけ回転 -->
<button aria-expanded="true" class="aria-expanded:rotate-180 transition-transform">

</button>
 
<!-- data-state="active" のとき下線 -->
<div data-state="active" class="data-[state=active]:underline">タブ</div>

修飾子は組み合わせて重ねられる

修飾子はチェーンできます。「md 以上の画面で、かつホバー時」のように条件を重ねられます。レスポンシブ修飾子(レスポンシブデザイン)やダークモード(ダークモード)とも自由に組み合わせ可能です。

<!-- md以上の画面でホバーしたときだけ背景を変える -->
<button class="bg-blue-600 md:hover:bg-blue-700">ボタン</button>
 
<!-- ダークモードでホバーしたとき -->
<a class="text-gray-700 dark:text-gray-300 dark:hover:text-white">リンク</a>

よくある状態修飾子 早見表

修飾子適用されるタイミング対応するCSS擬似クラス
hover:マウスを乗せている間:hover
focus:フォーカスされている間:focus
focus-visible:キーボードでフォーカスされた間:focus-visible
focus-within:内側の要素がフォーカスされた間:focus-within
active:クリック/タップしている瞬間:active
disabled:disabled 属性が付いているとき:disabled
checked:チェックされているとき:checked
first: / last:最初の / 最後の子要素:first-child
odd: / even:奇数番目 / 偶数番目:nth-child(odd)
group-hover:親(group)にホバーしたとき.group:hover &
peer-checked:兄弟(peer)がチェックされたとき.peer:checked ~ &

よくあるハマりどころ

状態修飾子のハマりどころを確認するイメージ

1. transition- を付け忘れてカクつく

hover: で色やサイズを変えても、transition-colorstransition-transform を付けないと一瞬で切り替わってしまいます。滑らかにするには通常時のクラスに transition-* を足します。

<!-- ❌ パッと切り替わる -->
<button class="bg-blue-600 hover:bg-blue-700">ボタン</button>
 
<!-- ⭕ 滑らかに変化 -->
<button class="bg-blue-600 hover:bg-blue-700 transition-colors">ボタン</button>

2. group-hover: が効かない

親要素に group クラスを付け忘れているケースがほとんどです。group-* は必ず group を付けた親とセットで使います。

3. peer の位置が逆

peer-* は、peer を付けた要素より後ろの兄弟にしか効きません。スタイルを変えたい要素を peer より後ろに置く必要があります。

4. hover: をタッチ端末の主要動線に使っている

前述のとおり、タッチ端末では hover: が機能しません。重要な操作はタップでも到達できるようにします。


ちゃんと使うためのポイント

  • 状態修飾子は 修飾子:ユーティリティ の形。裏側で擬似クラス付きのCSSが生成される
  • hover:transition-* はセットで使うと滑らかなインタラクションになる
  • フォームには必ず focus:ring-* / focus-visible:ring-* を実装してアクセシビリティを確保する
  • カードのホバーは group + group-hover:、トグルUIは peer + peer-checked: が定番
  • 修飾子はレスポンシブ(md:)やダークモード(dark:)と重ねられる

次の章では、dark: 修飾子を使った ダークモード 対応を学びます。コンポーネントとして組み立てる実践は コンポーネント設計 で扱います。