Mastering group and peer
Style an element based on the state of a parent (group) or a sibling (peer).
A utility normally reacts to its own state. group and peer break that limit so a child can react to its parent, and an element can react to a sibling.
group β react to an ancestor
Add group to a parent, then group-hover:, group-focus:, or group-data-[open]: to any descendant. Hover the card once and everything inside can respond together.
<a class="group">
<h3 class="group-hover:text-cyan-600">Title</h3>
<span class="opacity-0 group-hover:opacity-100">→</span>
</a>peer β react to a previous sibling
Mark an element peer; a later sibling can then use peer-checked:, peer-focus:, or peer-invalid:. This is how you build floating labels and custom checkboxes with zero JavaScript.
Named groups and peers
Nesting two groups? Give them names so they do not collide: group/item on the parent and group-hover/item: on the child target that specific group.
Order matters for peer: the utility only sees siblings that come before it in the DOM. Put the input first, the label after.
Example
When to use it
- A file list row highlights its delete button only when the row is hovered by using group on the li and group-hover:visible on the button.
- A floating label input moves its label upward when the input is focused by using peer on the input and peer-focus:-translate-y-5 on the label.
- A complex navigation uses named groups (group/nav) to scope group-hover effects so nested group elements do not interfere with each other.
More examples
Show action button on row hover
invisible hides the delete button from layout and interaction; group-hover:visible reveals it only when the parent row is hovered.
<li class="group flex items-center justify-between p-3 hover:bg-gray-50 rounded">
<span class="text-gray-800">document.pdf</span>
<button
class="invisible group-hover:visible text-sm text-red-500 hover:text-red-700"
>
Delete
</button>
</li>Floating label with peer
peer on the input exposes focus and content states; the label transitions upward via peer-focus: and stays up while text is present.
<div class="relative">
<input
id="name"
type="text"
placeholder=" "
class="peer w-full border-b-2 border-gray-300 px-0 py-2 text-gray-900
focus:border-indigo-500 focus:outline-none"
/>
<label
for="name"
class="absolute left-0 top-2 text-gray-500 text-sm
transition-all duration-200
peer-focus:-translate-y-6 peer-focus:text-xs peer-focus:text-indigo-600
peer-[:not(:placeholder-shown)]:-translate-y-6
peer-[:not(:placeholder-shown)]:text-xs"
>
Full Name
</label>
</div>Named groups for nested hover
Named groups (group/card and group/tag) allow independent hover scoping: card hover and tag hover each target only their own children.
<div class="group/card bg-white border rounded-lg p-4 hover:border-indigo-300">
<h3 class="font-semibold group-hover/card:text-indigo-600">Card Title</h3>
<div class="group/tag mt-2 inline-flex">
<span class="text-xs bg-gray-100 group-hover/tag:bg-indigo-100 px-2 py-1 rounded">
Tag
</span>
</div>
</div>
Discussion