Tailwind CSSのpeerとは?groupとの違い・peer-checked・peer-invalid・効かない原因まで解説

groupは親から子へ、peerは兄弟から兄弟へスタイルを伝える違いを示した図
  • このエントリーをはてなブックマークに追加

スポンサーリンク

「チェックされたら、別の要素の色を変えたい」「入力エラーのときだけメッセージを赤く出したい」——こういうUI、JavaScriptを書かずにできたら楽だと思いませんか?

Tailwind CSSには group という仕組みがあって、これを知っている方は多いはずです。でも、その兄弟分にあたる peer となると「名前は見たことあるけど違いがよくわからない」という方が多いのではないでしょうか。

group は「親にホバーしたら子の見た目を変える」のが得意ですが、フォームでは少し使いづらい場面があります。そんなときに登場するのが peer です。チェックボックス・ラジオボタン・入力エラー表示など、フォーム部品の状態を別の要素に伝えたいときに活躍します。「なぜ group とは別に peer があるのか」は、ここを押さえると一気に腑に落ちます。

この記事では、peergroup の違いを図で押さえたうえで、peer-checkedpeer-invalid を使った実例、よく使うバリアント一覧、そして「peer が効かない!」ときのチェックリストまで、コピペできるコードで解説します。Tailwindの導入がまだの方は RailsにTailwind CSS v4を導入する方法 から、全体像をつかみたい方は Tailwind CSSの使い方【初心者向け】 も合わせてどうぞ。

まず結論:groupは「親→子」、peerは「兄弟→兄弟」

細かい話に入る前に、いちばん大事なところだけ先に言ってしまいます。

  • groupの状態(hoverなど)を、その中の子要素のスタイルに反映する
  • peer:先に書いた兄弟の状態(checkedなど)を、その後ろの兄弟のスタイルに反映する

図にするとこうです。向き(だれの状態を、どこに伝えるか)がまったく違います。

groupは親から子へ、peerは兄弟から兄弟へスタイルを伝える違いを示した図
group は親→子、peer は兄弟→兄弟。状態を伝える向きが違う

「親をまるごとホバーしたら中身が反応してほしい」なら group、「あるフォーム部品の状態を、その隣の要素に伝えたい」なら peer。この使い分けさえ掴めば、あとは応用です。group をさらに入れ子にして使う方法は Tailwind CSS v3.2で追加された機能を使ってgroupを入れ子にする方法 で詳しく書いているので、group 側を深掘りしたい方はそちらへ。

peerの基本:チェックボックスでラベルの色を変える

いちばんシンプルな例から始めましょう。チェックボックスに peer クラスを付けると、その後ろにある要素から peer-checked: でチェック状態を参照できます。

<input type="checkbox" class="peer" />
<label class="text-slate-500 peer-checked:text-indigo-600">
  チェックすると色が変わります
</label>

ポイントは2つだけです。

  • 状態を「持つ側」(=チェックボックス)に peer を付ける
  • 状態に「反応する側」(=ラベル)に peer-checked: を付ける

これで、チェックを入れた瞬間にラベルの文字色が変わります。JavaScriptは一行も書いていません。checked だけでなく、状態に応じたバリアントがひと通り用意されています。

よく使うpeerバリアント一覧

peer-checked 以外にどんなのがあるの?」という方向けに、実務で出番の多いものをまとめておきます。基本は peer-(状態): の形で、状態の部分はおなじみのCSS擬似クラスに対応しています。

クラス効くタイミングよく使う場面
peer-checked:チェック/選択されたときチェックボックス・ラジオ・トグル
peer-focus:フォーカスされたとき入力中のラベル強調・フローティングラベル
peer-hover:ホバーされたとき兄弟をホバーしたときの補足表示
peer-invalid:入力が不正なときバリデーションエラー表示
peer-required:必須項目のとき「必須」マークの表示
peer-disabled:無効化されているとき使えない項目をグレーアウト
peer-placeholder-shown:未入力(プレースホルダー表示中)のとき未入力かどうかでの出し分け

このうち peer-checked:peer-invalid: が二大巨頭です。次の章で、それぞれ実例を見ていきます。

実例1:チェックボックスを「ボタン風」にする(peer-checked)

実務でよく使うのが、標準のチェックボックスを隠して、見た目をリッチにするパターンです。本物の <input>sr-only で隠しておき(=画面上は見えないがアクセシビリティは保たれる)、見た目用の <span>peer-checked: で切り替えます。

<label class="flex items-center gap-3 p-3 rounded-lg border border-slate-200 cursor-pointer">
  <input type="checkbox" class="peer sr-only" />

  <!-- 見た目のチェックボックス -->
  <span class="w-5 h-5 rounded border-2 border-slate-300
               peer-checked:bg-indigo-500 peer-checked:border-indigo-500"></span>

  <!-- ラベル -->
  <span class="text-slate-600
               peer-checked:text-indigo-600 peer-checked:font-bold">
    メール通知を受け取る
  </span>
</label>

チェックすると、四角が塗りつぶされ、ラベルも色付き・太字に変わります。実際の見た目はこんなイメージです(左:未チェック/右:チェック時)。

peer-checkedでチェック前と後のチェックボックスの見た目が変わる例
peer-checked: でチェック時だけ四角の塗りとラベルの色・太さが変わる

ラジオボタンでも同じ要領です。「選択中のカードだけ枠を強調する」といったカード選択UIも、この応用でJavaScriptなしに作れます。

実例2:フォームのバリデーション表示(peer-invalid)

もうひとつの定番が、入力エラーの表示です。まずはいちばんシンプルな形から覚えましょう。<input>peer を付けておけば、後ろのエラーメッセージを peer-invalid: で出し分けられます。peer-invalid: は、ブラウザのHTML5バリデーション(type="email"required など)が「不正」と判断したときに効きます。

STEP1:まずは peer-invalid だけ

<input
  type="email"
  required
  placeholder="you@example.com"
  class="peer px-3 py-2 rounded-lg border border-slate-300
         focus:border-indigo-500 focus:outline-none" />

<p class="invisible peer-invalid:visible mt-1 text-sm text-red-600">
  メールアドレスの形式が正しくありません
</p>

普段はメッセージを invisible で隠しておき、入力が不正になった瞬間に peer-invalid:visible で表示する、という流れです。見た目はこうなります。

peer-invalidでフォーム入力が不正なときに赤いエラー表示を出す例
peer-invalid: で入力が不正なときだけ赤枠とエラーメッセージを表示

まずはこれで十分動きます。peer-invalid:visible で不正なときだけ表示する」——この形をしっかり押さえてください。

STEP2:実務では「入力し始めてから」出す

STEP1のコードには、実は落とし穴があります。required な入力欄は「空っぽ」の時点でもinvalidなので、ページを開いた瞬間から(まだ何も入力していないのに)エラーが出てしまうのです。

そこで実務では「入力し始めてから(=空でなくなってから)だけ出したい」ことが多く、:placeholder-shown(プレースホルダーが見えている=未入力の状態)を組み合わせます。少しコードが複雑になるので、初めての方はSTEP1だけ覚えて、ここは「こういう書き方もある」と眺める程度でOKです。

<p class="invisible mt-1 text-sm text-red-600
          peer-[&:not(:placeholder-shown):invalid]:visible">
  メールアドレスの形式が正しくありません
</p>

peer-[...] は「任意のセレクタで peer の状態を細かく指定する」書き方で、角カッコの中の &peer 自身(=入力欄)を指します。:not(:placeholder-shown) で「未入力ではない」、:invalid で「不正」——つまり「何か入力されていて、かつ形式が不正」のときだけメッセージが出ます。placeholder 属性を付けておくのを忘れずに。

ハマりポイント①:peerは「先に書いた兄弟」しか参照できない

peer でいちばん多いつまずきが、これです。peer-* は、peer が付いた要素より後ろにある兄弟にしか効きません。これはCSSの仕組み(後続兄弟を選ぶセレクタ)に由来する制約で、Tailwindの都合ではありません。

<!-- ❌ 効かない:label が input より前にある -->
<label class="peer-checked:text-indigo-600">ラベル</label>
<input type="checkbox" class="peer" />

<!-- ✅ peer を先に書く -->
<input type="checkbox" class="peer" />
<label class="peer-checked:text-indigo-600">ラベル</label>

peer-checked: を書いたのに反応しない」ときは、まずHTMLの順番を疑ってください。反応させたい要素は、必ず peer後ろに置きます。また、両者は同じ親の中の兄弟である必要があります(親をまたいで上の階層には伝わりません)。階層をまたいで親の状態を子に伝えたいなら、それは peer ではなく group の出番です。

ハマりポイント②:peerが複数あるときは「名前」で区別する

同じ階層に peer が2つ以上あると、peer-checked: はどれの状態を見ればいいのか区別できません。そんなときは、group と同じく名前付きpeer(Tailwind v3.2以降)を使います。peer/名前 で名前を付け、peer-checked/名前: で特定のpeerだけを参照します。

<input type="checkbox" class="peer/published" />
<input type="checkbox" class="peer/draft" />

<p class="hidden peer-checked/published:block">公開中です</p>
<p class="hidden peer-checked/draft:block">下書きです</p>

これで「publishedにチェックが入ったら『公開中です』、draftなら『下書きです』」と、それぞれ独立して反応させられます。複数のトグルが絡むUIでは、名前を付けておくと混線せずに済みます。

peerが効かないときのチェックリスト

peer-checked: を書いたのに反応しない!」——peer でつまずいたら、まずこの順に確認してください。だいたいこのどれかに当てはまります。

  • 反応させたい要素が peer より「後ろ」に書かれているか(前にあると効かない/ハマりポイント①)
  • 両者は同じ親の中の兄弟か(親をまたいで上の階層には伝わらない)
  • peer を付けた要素が、本当にその状態になるかpeer-checked: なら相手は checkboxradio か)
  • 同じ階層に peer が複数ないか(あるなら名前付き peer/名前 で区別/ハマりポイント②)
  • そのクラスがビルドに含まれているか(動的に組み立てたクラス名はTailwindが検出できず生成されないことがある。content の対象に入っているか確認)

特に多いのが、いちばん上の「順番」と、いちばん下の「クラスが生成されていない」の2つです。順番は先ほどのハマりポイント①で詳しく触れたとおり、生成まわりは「書いたクラスがそもそもCSSに出ているか」をDevToolsで確認すると早いです。

group-hoverとpeerの違い、結局どっち?

この記事の本丸、grouppeer の違いを表で整理します。迷ったら「だれの状態を、どこに伝えたいか」で選んでください。

比較grouppeer
状態を持つ側親要素(group兄弟要素(peer
反応する側中の子要素後ろの兄弟要素
向き親 → 子兄弟 → 兄弟
得意な場面hover系のUIフォーム系のUI
よく使う例カード全体をhover→中のボタンや矢印を強調チェックボックス/入力欄→ラベルやエラー表示
代表バリアントgroup-hover: group-focus:peer-checked: peer-invalid: peer-focus:

覚え方はシンプルです。迷ったら、こう判断してください。

  • hover系(カードやボタンをホバーして中身を変える)→ group
  • フォーム系(チェックや入力エラーで隣を変える)→ peer

もちろん両方を組み合わせることもできます(例:カード全体は group、中のチェックボックスは peer)。レイアウトやカードUIそのものの組み立て方は Tailwind CSSの使い方【初心者向け】 に、group を入れ子にする応用は groupを入れ子にする方法 にまとめているので、合わせて読むとTailwindのstate系がひと通り押さえられます。

まとめ

peer のポイントを振り返ります。

  • group=親→子、peer=兄弟→兄弟。迷ったらhover系→group、フォーム系→peer
  • 状態を持つ側に peer、反応する側に peer-checked: / peer-invalid: などを付ける
  • peer-invalid: はまず単独で覚え、実務では :placeholder-shown と組み合わせる
  • 効かないときはチェックリストで確認(特に「順番」と「クラスが生成されているか」)
  • peer が複数あるときは 名前付きpeerpeer/名前)で区別する

チェックボックスやフォームのちょっとしたインタラクションは、peer を覚えるだけでJavaScriptなしにかなり作れるようになります。まずは「チェックしたらラベルの色が変わる」最小例から手を動かしてみてください。

関連記事として、親の状態を入れ子の子に伝える groupを入れ子にする方法、レイアウトやカードUIの組み立てを一気に学べる Tailwind CSSの使い方【初心者向け】 もどうぞ。

スポンサーリンク

  • このエントリーをはてなブックマークに追加