Tailwindのgroup-*とpeer-*を活用すればJSの活用は大きく減る!...かもしれない
Tailwindを活用すればJSの活用は大きく減る!...かもしれない
本記事のサマリ
TailwindCSSの group-*と peer-*という機能を使うと、これまでJavaScriptで書いていたような親子・兄弟要素間の動的なスタイリングを、CSSだけで実現できるようになります。ホバー時のアイコン色変更やフォームのエラー表示切替など、実務でよくある要件をJavaScriptなしで実装できるため、コードがシンプルになって保守性も向上します。もちろんすべてのJSが不要になるわけではありませんが、確実に使用頻度は減らせると思います。
なぜ今この話をするのか
フロントエンド開発をしていると、「親要素をホバーしたら子要素の見た目を変えたい」とか「入力エラーの時だけメッセージを表示したい」といった要件によく出会います。従来はCSSで親子セレクタを書いたり、JavaScriptでクラスの付け替えをしたりしていましたが、Tailwindを使えばもっと直感的に、しかもJavaScriptを使わずに解決できるケースが増えています。
実際に使ってみると、これは単なる「便利機能」以上の価値があると感じます。コードが読みやすくなるし、状態管理も楽になる。特に小さなインタラクションについては、わざわざJavaScriptを書く必要がなくなるんです。
group-*バリアント:親の状態を子に伝える
基本的な仕組み
まず理解しておきたいのは、group-*バリアントの基本的な仕組みです。親要素に groupクラスをつけることで、その子要素で group-hover:や group-focus:といったバリアントが使えるようになります。
たとえば、ナビゲーションのリンクをホバーした時に、中のアイコンとテキストの色を変えたいとします。
<a href="#" class="group flex items-center p-4 hover:bg-gray-50">
<svg class="w-6 h-6 text-gray-400 group-hover:text-blue-500 transition-colors">
<!-- アイコン -->
</svg>
<span class="ml-3 text-gray-700 group-hover:text-gray-900">
Dashboard
</span>
</a>
この例では、リンク全体(<a>タグ)に groupクラスをつけて、アイコンとテキストに group-hover:バリアントを適用しています。リンクをホバーすると、アイコンが青色に、テキストがより濃いグレーに変わります。
従来のCSSなら :hover擬似クラスと子セレクタを組み合わせる必要がありましたが、Tailwindなら直接子要素に group-hover:を書けば済みます。これだけでも結構楽になりますね。
複数のgroupを扱う場合
実際の開発では、groupが入れ子になることもあります。そんな時は名前をつけて区別できます。
<article class="group/card bg-white hover:bg-gray-50">
<div class="group/header">
<h2 class="group-hover/card:text-blue-600">記事タイトル</h2>
<button class="opacity-0 group-hover/header:opacity-100">
編集
</button>
</div>
</article>
ここでは外側の記事カードに group/card、内側のヘッダー部分に group/headerと名前をつけています。タイトルは「カード全体のホバー」で反応し、編集ボタンは「ヘッダー部分のホバー」でのみ表示されます。
この命名機能があることで、複雑なレイアウトでも混乱せずに制御できるようになります。
peer-*バリアント:兄弟要素の状態を見て反応する
フォームでの実用例
peer-*バリアントは兄弟要素間の連携に使います。特にフォームでの活用頻度が高く、入力エラーメッセージの表示切替などでよく使われます。
<div>
<input
type="email"
class="peer w-full px-3 py-2 border border-gray-300 rounded-md
invalid:border-red-500 focus:ring-2 focus:ring-blue-500"
placeholder="メールアドレス"
required
/>
<p class="mt-1 text-sm text-red-500 invisible peer-invalid:visible">
正しいメールアドレスを入力してください
</p>
</div>
この例では、入力フィールドに peerクラスをつけて、エラーメッセージに peer-invalid:visibleを適用しています。ブラウザの標準バリデーションで入力が無効になった時だけ、エラーメッセージが表示されます。
重要なのは、peerクラスをつけた要素の次に来る兄弟要素が反応できるということです。DOM構造を意識して書く必要はありますが、JavaScriptでエラー状態を管理する手間が省けます。
より複雑な例:ラジオボタンでの表示切替
peer-*バリアントは、ラジオボタンやチェックボックスでの表示切替でも威力を発揮します。
<fieldset>
<input id="custom" type="radio" name="plan" class="peer/custom sr-only" />
<label for="custom" class="peer-checked/custom:bg-blue-50 px-4 py-2 rounded-md cursor-pointer">
カスタムプラン
</label>
<div class="hidden peer-checked/custom:block mt-4 p-4 bg-gray-50 rounded-md">
<!-- カスタムプラン選択時のみ表示される詳細設定 -->
<p>プラン詳細をここに表示</p>
</div>
</fieldset>
ラジオボタンがチェックされた時だけ詳細エリアを表示する、という動作をJavaScript抜きで実現しています。ラジオボタン自体は sr-onlyで視覚的に隠して、ラベルをクリック可能にしているのもポイントです。
実際に使ってみての感想
これらの機能を実務で使ってみて感じるのは、「小さなインタラクションのためだけにJavaScriptを書く機会が本当に減った」ということです。もちろん複雑な状態管理や非同期処理は相変わらずJavaScriptが必要ですが、単純なスタイルの切替程度ならTailwindだけで完結できるケースが多くなりました。
特にプロトタイピングの段階では、JavaScriptを書く前に「これ、Tailwindだけでできるかも?」と考える癖がつきました。
注意点とベストプラクティス
ただし、何でもかんでもTailwindで解決しようとするのは危険です。アクセシビリティを考慮する必要がある場合や、複雑な状態変更が絡む場合は、素直にJavaScriptを使った方が良いケースもあります。
また、peer-*バリアントはDOMの構造に依存するため、HTMLの順序を変更する際は注意が必要です。チーム開発では、このあたりの制約をドキュメントに残しておくと良いでしょう。
※ 加えていうならば、せっかくTailswindでclass名を付けなくて良い、「ユーティリティクラス」を採用してるのに、groupで複雑に命名をしていたら本末転倒です...😱
まとめ:適材適所で活用を
Tailwindのバリアント機能を使えば、確実にJavaScriptの使用頻度は減らせます。特に group-*と peer-*は実務での出番が多く、覚えておいて損はありません。
ただし、これは「JavaScriptが不要になる」という話ではなく、「適切な場面で適切な技術を使い分けられるようになる」という話だと思います。シンプルなインタラクションはTailwindで、複雑な処理はJavaScriptで。そのバランス感覚が、より良いフロントエンド開発につながるのではないでしょうか✨
詳しい仕様については、公式ドキュメントも参考にしてみてください。
株式会社StellarCreate(stellar-create.co.jp)のエンジニアブログです。 プロダクト指向のフルスタックエンジニアを目指す方募集中です! カジュアル面談で気軽に雑談しましょう!→ recruit.stellar-create.co.jp/
Discussion