TailwindCSS JS不要で状態管理ができるpeerプロパティについて
こんにちは、nhです。
6月に入り、梅雨らしい天気の日も増えてきましたね。
外出の予定が立てづらい時期ですが、その分まとまった時間を使ってフロントエンドの知識を深めるには良い季節かもしれません。
今回は、TailwindCSSの「peer」プロパティについてご紹介します。peerは、フォーム要素などの状態に応じて別の要素のスタイルを変更できる機能です。
通常CSSでは兄弟セレクタ(`~`)などを利用して実現しますが、TailwindCSSではpeerとpeer-*を利用することで、HTML上で直感的に状態管理を行うことができます。
特にフォーム周りの実装では活躍する機会が多く、JavaScriptを書かずに実現できるUIも少なくありません。
それでは、具体的な使い方を見ていきましょう。
1. チェックボックスの状態に応じてラベルの見た目を変更する
まずは最も基本的な使い方です。
チェックボックスにpeerを付与し、その状態をラベル側で参照します。
<input
type="checkbox"
id="agree"
class="peer hidden"
/>
<label
for="agree"
class="cursor-pointer rounded border px-4 py-2
peer-checked:bg-blue-500
peer-checked:text-white"
>
利用規約に同意する
</label>
チェック状態に応じてラベルの見た目を変更できます。
カスタムチェックボックスやカード選択UIなどでよく利用されるパターンです。
2. フォームバリデーションのエラーメッセージを表示する
入力値の検証状態に応じてメッセージを表示することもできます。
<input
type="email"
class="peer border p-2"
placeholder="メールアドレス"
required
/>
<p class="hidden text-sm text-red-500 peer-invalid:block">
正しいメールアドレスを入力してください。
</p>
入力内容が不正な場合のみエラーメッセージが表示されます。
簡単なバリデーションであれば、JavaScriptを使わずに実装できます。
3. フローティングラベルを実装する
最近のフォームUIでよく見かけるフローティングラベルも、`peer`を利用して実装できます。
<div class="relative">
<input
type="text"
placeholder=" "
class="peer w-full border p-4"
/>
<label
class="absolute left-4 top-4 transition-all
peer-focus:top-1
peer-focus:text-xs
peer-[:not(:placeholder-shown)]:top-1
peer-[:not(:placeholder-shown)]:text-xs"
>
お名前
</label>
</div>
フォーカス時や入力済みの状態に応じてラベルが移動するため、より洗練されたフォームUIを実現できます。
※Tailwind CSS v4系を前提にする場合は、`peer-[:not(:placeholder-shown)]` のようなArbitrary Variantが利用できます。
4. よく使うpeerバリエーション一覧
peerにはさまざまな状態を監視するためのバリエーションが用意されています。
実務でよく利用するものをまとめてみました。
| クラス | 発火条件 | 主な用途 |
peer-focus:* | 要素がフォーカスされた時 | 入力補助 |
peer-hover:* | 要素にホバーした時 | 補足情報表示 |
peer-disabled:* | 要素が無効化された時 | 操作制御 |
peer-checked:* | チェック状態の時 | チェックボックス、ラジオボタン |
peer-invalid:* | 入力値が不正な時 | バリデーション |
◆peer-focus
<input
type="text"
class="peer border p-2"
/>
<p class="peer-focus:text-blue-500">
お名前を入力してください。
</p>
入力欄にフォーカスした際に説明文の色を変更できます。
◆peer-hover
<button class="peer">
ヘルプ
</button>
<p class="hidden peer-hover:block">
詳細な説明を表示しています。
</p>
ホバー時に補足情報を表示できます。
◆peer-disabled
<input
type="text"
disabled
class="peer border p-2"
/>
<p class="peer-disabled:text-red-500">
この項目は編集できません。
</p>
無効化状態に応じてメッセージの見た目を変更できます。
◆peer-checked
<input
type="checkbox"
class="peer"
/>
<label class="peer-checked:text-green-500">
プランを選択
</label>
チェック状態を利用してUIを切り替えられます。
◆peer-invalid
<input
type="email"
required
class="peer"
/>
<p class="hidden peer-invalid:block text-red-500">
正しいメールアドレスを入力してください。
</p>
入力値が不正な場合のみエラーメッセージを表示できます。
5. peerとgroupの違い
`peer`とよく比較される機能として`group`があります。
◆peer
兄弟要素の状態を監視する場合に使用します。
<input class="peer" />
<p class="peer-focus:text-blue-500">
説明文
</p>
◆group
親要素の状態を監視する場合に使用します。
<div class="group">
<h3 class="group-hover:text-blue-500">
タイトル
</h3>
</div>
◆使い分け
| 利用シーン | 推奨 |
| フォーム入力状態を参照したい | peer |
| チェックボックスの状態を参照したい | peer |
| 親要素のホバー状態を参照したい | group |
| カード全体のホバー演出を行いたい | group |
6. まとめ
TailwindCSSのpeerは、ある要素の状態を基準に別要素のスタイルを変更できる便利な機能です。
特にフォーム周りでは、
- チェック状態の切り替え
- バリデーション表示
- フローティングラベル
- フォーカス時の補助表示
など、多くのUIをJavaScriptなしで実装できます。
また、groupとの違いを理解しておくことで、よりシンプルで保守しやすいコードを書くことができます。
フォーム実装やインタラクティブなUIを作成する際は、ぜひ活用してみてください。
最後に、今回のサンプルコードを掲載します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TailwindCSS peer サンプル集</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-100 py-10 px-4">
<div class="mx-auto max-w-5xl">
<header class="mb-10">
<h1 class="text-4xl font-bold mb-2">
TailwindCSS peer プロパティ サンプル集
</h1>
<p class="text-slate-600">
ブログ記事で紹介した peer の実装例をまとめています。
</p>
</header>
<div class="space-y-8">
<!-- 1 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-4 text-2xl font-bold">
1. peer-checked
</h2>
<input
id="agree"
type="checkbox"
class="peer hidden"
>
<label
for="agree"
class="inline-block cursor-pointer rounded-lg border border-slate-300 px-5 py-3 transition-all
peer-checked:bg-blue-500
peer-checked:text-white
peer-checked:border-blue-500"
>
利用規約に同意する
</label>
<p class="mt-3 text-sm text-slate-500">
チェック状態に応じてラベルの見た目が変化します。
</p>
</section>
<!-- 2 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-4 text-2xl font-bold">
2. peer-invalid
</h2>
<div class="max-w-md">
<input
type="email"
required
placeholder="メールアドレス"
class="peer w-full rounded-lg border border-slate-300 p-3"
>
<p class="mt-2 hidden text-sm text-red-500 peer-invalid:block">
正しいメールアドレスを入力してください。
</p>
</div>
<p class="mt-3 text-sm text-slate-500">
メールアドレス形式でない場合にエラーメッセージが表示されます。
</p>
</section>
<!-- 3 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-4 text-2xl font-bold">
3. フローティングラベル
</h2>
<div class="relative max-w-md">
<input
id="name"
type="text"
placeholder=" "
class="peer w-full rounded-lg border border-slate-300 px-4 pt-6 pb-2"
>
<label
for="name"
class="absolute left-3 top-4 bg-white px-1 text-slate-500 transition-all duration-200
peer-focus:top-0
peer-focus:text-xs
peer-focus:text-blue-500
peer-[:not(:placeholder-shown)]:top-0
peer-[:not(:placeholder-shown)]:text-xs"
>
お名前
</label>
</div>
<p class="mt-3 text-sm text-slate-500">
フォーカス時や入力済み状態でラベルが上へ移動します。
</p>
</section>
<!-- 4 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-4 text-2xl font-bold">
4. peer-focus
</h2>
<div class="max-w-md">
<input
type="text"
placeholder="お名前"
class="peer w-full rounded-lg border border-slate-300 p-3"
>
<p class="mt-2 text-sm text-slate-500 transition-colors peer-focus:text-blue-500">
お名前を入力してください。
</p>
</div>
<p class="mt-3 text-sm text-slate-500">
入力欄にフォーカスすると説明文の色が変わります。
</p>
</section>
<!-- 5 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-4 text-2xl font-bold">
5. peer-hover
</h2>
<div>
<button
class="peer rounded-lg bg-slate-800 px-5 py-3 text-white"
>
ヘルプ
</button>
<p class="mt-2 hidden rounded bg-slate-100 p-3 text-sm text-slate-600 peer-hover:block">
詳細な説明を表示しています。
</p>
</div>
<p class="mt-3 text-sm text-slate-500">
ボタンにホバーすると補足説明が表示されます。
</p>
</section>
<!-- 6 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-4 text-2xl font-bold">
6. peer-disabled
</h2>
<div class="max-w-md">
<input
type="text"
disabled
value="編集不可"
class="peer w-full rounded-lg border border-slate-300 bg-slate-100 p-3"
>
<p class="mt-2 text-sm text-slate-500 peer-disabled:text-red-500">
この項目は現在編集できません。
</p>
</div>
<p class="mt-3 text-sm text-slate-500">
disabled状態に応じてメッセージの色を変更できます。
</p>
</section>
<!-- 7 -->
<section class="rounded-xl bg-white p-6 shadow">
<h2 class="mb-6 text-2xl font-bold">
7. peer と group の違い
</h2>
<div class="grid gap-6 md:grid-cols-2">
<!-- peer -->
<div class="rounded-lg border p-5">
<h3 class="mb-3 text-lg font-semibold">
peer
</h3>
<input
type="text"
placeholder="フォーカスしてください"
class="peer w-full rounded border p-3"
>
<p class="mt-2 text-slate-500 peer-focus:text-blue-500">
フォーカスすると色が変わります
</p>
<p class="mt-4 text-sm text-slate-400">
兄弟要素の状態を監視
</p>
</div>
<!-- group -->
<div class="group rounded-lg border p-5 transition">
<h3 class="mb-3 text-lg font-semibold group-hover:text-blue-500">
group
</h3>
<p class="text-slate-500 group-hover:text-blue-500">
カード全体にホバーしてください
</p>
<p class="mt-4 text-sm text-slate-400">
親要素の状態を監視
</p>
</div>
</div>
</section>
</div>
</div>
</body>
</html>