Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Group and peer variants

The usual hover: reacts to hovering over the element itself. Often you need something else: hover the whole card and the button inside changes; check a box and the label next to it changes. That is what the group-* variants (parent state) and peer-* variants (sibling state) are for.

group: the parent's state

The parent gets the group class, and the children get group-hover:, group-focus: and other variants:

<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
 
<a href="#" class="group block max-w-xs rounded-xl border border-gray-200 p-4 hover:border-indigo-400">
  <h2 class="font-semibold group-hover:text-indigo-600">Wireless headphones</h2>
  <p class="text-sm text-gray-500">More about this product</p>
</a>

Hover anywhere on the card — the name turns blue, even if the cursor is over the description.

A product card with group

The image zooms in a little and the button appears while the cursor is over the card:

<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
 
<div class="group max-w-xs overflow-hidden rounded-xl bg-white shadow-lg">
  <div class="relative overflow-hidden">
    <img src="/media/photo-400x250.jpg" alt="Headphones" class="h-44 w-full object-cover transition group-hover:scale-105">
    <button class="absolute right-3 bottom-3 rounded-lg bg-white px-3 py-1 text-sm font-medium opacity-0 shadow group-hover:opacity-100">
      Add to cart
    </button>
  </div>
  <p class="p-4 font-semibold">Headphones — $79</p>
</div>

transition makes the zoom smooth — transitions are the next lesson.

Named groups

When groups are nested, group-hover: reacts to any of them. A group can be named: group/item, and the child gets group-hover/item:visible. Then the style depends only on the ancestor you mean.

peer: a sibling's state

peer goes on one element, and a peer-* variant on a sibling that comes after it. The most common case is an error hint next to a field:

<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
 
<label class="block max-w-xs">
  <span class="text-sm font-medium">Email</span>
  <input type="email" class="peer mt-1 w-full rounded-lg border border-gray-300 px-3 py-2" placeholder="anna@example.com">
  <span class="mt-1 hidden text-sm text-red-600 peer-invalid:block">Enter a valid address</span>
</label>

Type some text without an @ sign — a red hint appears below the field. peer-invalid:block shows it when the sibling field fails the browser's validation.

peer-checked: for checkboxes and switches and peer-focus: for field labels work the same way.

Common mistake

Putting the element with peer-* before the peer element:

<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
 
<label>
  <span class="text-gray-500 peer-checked:text-green-600">Subscribe to news</span>
  <input type="checkbox" class="peer">
</label>

Check the box — the label does not turn green. peer-* is built on the ~ sibling selector, which only sees following elements. Put the element with the peer class earlier in the markup.

Thanks for reading!

Donate to the site — we work hard for you.