Group and Peer Variants: Styling by Parent and Sibling State

Intermediate
12 min

Group and Peer Variants: Styling by Parent and Sibling State

Plain hover: and focus: style the element being hovered or focused. Real interfaces often need more: a card whose title changes color when the whole card is hovered, or a label that turns red when the input beside it is invalid. Tailwind solves both with group and peer. In this lesson you will learn how they work, how to name them for nested layouts, and how has-* and in-* extend the idea.

group: react to an ancestor

Add group to the parent and group-<state>: to a descendant:

html
<article class="group relative overflow-hidden rounded-xl"> <img class="transition group-hover:scale-105" src="photo.jpg" alt="" /> <div class="absolute inset-0 flex items-end bg-black/0 p-4 group-hover:bg-black/40"> <span class="translate-y-2 opacity-0 transition group-hover:translate-y-0 group-hover:opacity-100"> View project </span> </div> </article>

Every state variant has a group form: group-hover:, group-focus:, group-focus-within:, group-active:, group-disabled:, group-open:, even group-first: and group-odd:. The generated selector is .group:hover .group-hover\:scale-105 — pure CSS, no JavaScript. group-focus-within: is ideal for search boxes: the wrapper gets a ring when the input inside is focused.

Named groups for nesting

When groups nest, group-hover: reacts to the nearest one. To target a specific ancestor, name it with a slash and reuse the name in the variant:

html
<li class="group/item flex justify-between p-3 hover:bg-gray-50"> <span>Invoice #1042</span> <a class="group/edit invisible group-hover/item:visible" href="#"> Edit <span class="group-hover/edit:translate-x-1 inline-block transition">→</span> </a> </li>

The edit link appears when the row is hovered; the arrow moves only when the link itself is hovered. Names are arbitrary words that describe the component.

peer: react to a previous sibling

peer is group for siblings. Mark the element whose state matters with peer, then style any later sibling with peer-<state>::

html
<div class="relative"> <input id="email" type="email" placeholder=" " required class="peer w-full rounded-md border px-3 pt-5 pb-2 invalid:border-red-500" /> <label for="email" class="absolute top-1 left-3 text-xs text-gray-500 transition-all peer-placeholder-shown:top-3.5 peer-placeholder-shown:text-base peer-focus:top-1 peer-focus:text-xs peer-focus:text-blue-600"> Email address </label> <p class="mt-1 hidden text-sm text-red-600 peer-invalid:block">Enter a valid email.</p> </div>

This floating-label pattern needs no JavaScript: peer-placeholder-shown: detects an empty input, peer-focus: lifts the label, and peer-invalid: reveals the error. peer-checked: builds custom checkboxes and toggles. Named peers (peer/terms, peer-checked/terms:) disambiguate several inputs in one parent.

peer compiles to the sibling combinator ~, which only looks forward, so the peer element must come before the elements that react to it. If a design needs the label first, swap the visual order with flex-row-reverse.

has-* and in-*: no extra classes

Two newer variants cover cases that used to need group or peer:

| Variant | Meaning | Example | |---|---|---| | has-checked: | This element contains a checked input | has-checked:ring-2 on a label | | has-[img]: | Contains a matching selector | has-[img]:p-0 on a card | | in-focus: | Any ancestor is focused | Same as group-focus: without adding group | | in-[.dark]: | Inside an element matching the selector | Theme-scoped styles |

has-* compiles to the :has() selector, so a <label class="has-checked:border-blue-600"> wrapping a radio button styles itself based on its own child. group-has-[…]: and peer-has-[…]: combine the two ideas.

Common mistakes

  • Placing the reacting element before the peer in the DOM; sibling selectors only look forward.
  • Forgetting the group class on the parent — the group-hover: variant silently does nothing.
  • Revealing essential controls with group-hover: only; add group-focus-within: for keyboard users.
Quick Quiz
Question 1 of 2

Which class must the parent have for `group-hover:text-blue-600` on a child to work?

Key Takeaways

  • group on an ancestor enables group-hover:, group-focus-within: and other group-* variants on descendants.
  • peer on an earlier sibling enables peer-checked:, peer-invalid:, peer-placeholder-shown: and more on later siblings.
  • Name groups and peers (group/name, group-hover/name:) when they nest or repeat.
  • has-* styles an element based on its own children; in-* reacts to any ancestor without a group class.
  • Always pair hover-revealed controls with a focus variant for keyboard access.

Next lesson: Structural, Data and ARIA Variants — target first and last children, nth-* positions, data-* and aria-* attributes, and pseudo-elements.

Group and Peer Variants: Styling by Parent and Sibling State - Tailwind CSS | CodeYourCraft | CodeYourCraft