Tailwind CSS 34 🎨 Parent and Sibling Targeting (group-* and peer-* Variants)
A variant applies a utility based on the element’s own state. The hover: variant applies when the element is hovered. But sometimes the state belongs to a different element. A card’s child should change when the card is hovered. A label should change when the input next to it is focused. A dropdown should open when the button that triggers it is clicked. The group-* and peer-* variants handle these cases. The group-* variant styles a child based on a parent’s state. The peer-* variant styles a sibling based on another sibling’s state.
Key point: The group class marks a parent. The group-* variant on a child applies a utility when the parent is in the specified state. The peer class marks a sibling. The peer-* variant on a later sibling applies a utility when the earlier sibling is in the specified state. The peer must come before the target in the DOM, and the two must share a parent.
Why group and peer variants matter
The parent-state problem. A card with an image, a title, and a description should change its shadow when the card is hovered. The hover is on the parent, but the shadow is on the parent, and the title color is on the child. Without the group-* variant, the child would need its own hover, which is not the same thing. The group-hover: variant applies the child’s style when the parent is hovered.
The sibling-state problem. A form input and its label are siblings. The label should change color when the input is focused. Without the peer-* variant, the label would need JavaScript to track the input’s focus. The peer-focus: variant applies the label’s style when the input is focused.
The composition problem. A group can contain a peer. A card (group) with an input (peer) and a label. The label uses peer-focus:, and the card uses group-hover:. The two variants compose, and the structure stays declarative.
The named-group problem. A page can have nested groups. An outer group and an inner group. Without names, the group-hover: on a child applies to the nearest group. The named forms—group/card and group-hover/card:—let a child target a specific group.
The JavaScript problem. Before the group and peer variants, the parent-state and sibling-state styling required JavaScript. A state variable, an event handler, a conditional class. The variants remove the JavaScript. The state is the pseudo-class, and the browser applies it.
The accessibility problem. The peer variant is the standard pattern for the floating label. The label moves and the input is styled based on the input’s state. The accessibility is preserved because the label is still associated with the input.
a. The group variant
The group class marks a parent. The group-* variant on a child applies when the parent is in the state.
<div class="group">
<h2 class="group-hover:text-blue-600">Title</h2>
<p class="group-hover:text-gray-700">Description</p>
</div>
The <div> is the group. The <h2> and <p> are the children. When the parent is hovered, the title and description change color. The hover is on the parent, and the children react.
The group variant works with any state variant.
| Group Variant | Applies When the Parent Is |
|---|---|
group-hover: | Hovered |
group-focus: | Focused |
group-focus-within: | Or contains focus |
group-active: | Active |
group-disabled: | Disabled |
group-checked: | Checked |
group-open: | Open (<details> or <dialog>) |
group-invalid: | Invalid |
<button class="group bg-gray-100 p-4">
<span class="group-hover:underline">Click me</span>
</button>
The button is the group. The span is the child. When the button is hovered, the span is underlined. The hover is on the button, and the span reacts.
The group can be used with a state that is not hover.
<fieldset class="group" disabled>
<legend class="group-disabled:opacity-50">Options</legend>
<input type="text" />
</fieldset>
The fieldset is the group, and it is disabled. The group-disabled: applies to the legend when the group is disabled. The label dims when the fieldset is disabled.
The group variant composes with other variants.
<div class="group">
<p class="group-hover:md:text-blue-600">Text</p>
</div>
The group-hover:md:text-blue-600 applies the blue text on medium screens and up, when the group is hovered. The order of the variants is the group first, then the breakpoint.
The named group is used when there are nested groups.
<div class="group/card">
<div class="group/inner">
<p class="group-hover/card:text-blue-600">Card hover</p>
<p class="group-hover/inner:text-red-600">Inner hover</p>
</div>
</div>
The group/card and group/inner name the two groups. The group-hover/card: applies when the card is hovered. The group-hover/inner: applies when the inner group is hovered. Without the names, both would target the nearest group.
The named group is also used when a child is not a direct descendant of the group.
<div class="group">
<div>
<div>
<p class="group-hover:text-blue-600">Deep child</p>
</div>
</div>
</div>
The group-hover: applies to any descendant of the group, not just the direct children. The variant works through the nesting.
b. The peer variant
The peer class marks a sibling. The peer-* variant on a later sibling applies when the earlier sibling is in the state.
<input type="email" class="peer" />
<p class="peer-invalid:text-red-500">Invalid email</p>
The <input> is the peer. The <p> is the target. When the input is invalid, the paragraph is red. The peer is the input, and the sibling reacts.
The peer must come before the target in the DOM. The CSS uses the general sibling combinator, which matches later siblings only.
<!-- CORRECT: peer before target -->
<input class="peer" />
<span class="peer-focus:text-blue-600">Label</span>
<!-- WRONG: target before peer -->
<span class="peer-focus:text-blue-600">Label</span>
<input class="peer" />
The second example does not work because the target comes before the peer. The CSS selector cannot match an earlier sibling.
The peer variant works with any state variant.
| Peer Variant | Applies When the Peer Is |
|---|---|
peer-hover: | Hovered |
peer-focus: | Focused |
peer-focus-visible: | Focus-visible |
peer-active: | Active |
peer-disabled: | Disabled |
peer-checked: | Checked |
peer-invalid: | Invalid |
peer-valid: | Valid |
peer-placeholder-shown: | Showing placeholder |
peer-required: | Required |
The peer-checked variant is the standard for custom checkboxes.
<label class="flex items-center gap-2">
<input type="checkbox" class="peer sr-only" />
<span class="w-5 h-5 border rounded peer-checked:bg-blue-600 peer-checked:border-blue-600" />
<span class="peer-checked:font-bold">Accept terms</span>
</label>
The input is visually hidden with sr-only. The span acts as the checkbox, and the label text is bold when the input is checked. The peer-checked variant drives the styling.
The peer-placeholder-shown variant is the standard for the floating label.
<div class="relative">
<input
class="peer w-full border rounded px-3 py-2 placeholder-transparent"
placeholder="Email"
/>
<label
class="absolute left-3 top-2 text-gray-500 transition-all
peer-placeholder-shown:top-2 peer-placeholder-shown:text-gray-400
peer-focus:-top-3 peer-focus:text-blue-500 peer-focus:text-xs"
>
Email
</label>
</div>
The label is inside the input when the placeholder is shown. When the input is focused, the label moves up and shrinks. The two peer variants—peer-placeholder-shown: and peer-focus:—control the position.
The peer variant composes with the group variant.
<div class="group">
<input class="peer" />
<span class="peer-focus:text-blue-600 group-hover:text-gray-700">Label</span>
</div>
The span reacts to the input’s focus (peer) and the parent’s hover (group). The two variants are independent, and both can apply.
The named peer is used when there are multiple peers.
<div>
<input class="peer/email" type="email" />
<input class="peer/password" type="password" />
<p class="peer-invalid/email:text-red-500">Invalid email</p>
<p class="peer-invalid/password:text-red-500">Invalid password</p>
</div>
The peer/email and peer/password name the two peers. The peer-invalid/email: applies when the email input is invalid. The peer-invalid/password: applies when the password input is invalid.
c. The patterns
Card with hover.
<div class="group rounded-lg border p-4 hover:shadow-lg transition-shadow">
<img class="group-hover:scale-105 transition-transform" />
<h3 class="group-hover:text-blue-600">Title</h3>
<p class="text-gray-500">Description</p>
</div>
The card is the group. The image scales and the title changes color when the card is hovered. The card’s shadow also changes.
Floating label.
<div class="relative">
<input class="peer w-full border rounded px-3 py-2 placeholder-transparent" placeholder="Email" />
<label class="absolute left-3 top-2 transition-all peer-placeholder-shown:top-2 peer-focus:-top-3 peer-focus:text-xs">
Email
</label>
</div>
The label floats above the input when the input is focused or has a value.
Custom checkbox.
<label class="flex items-center gap-2">
<input type="checkbox" class="peer sr-only" />
<span class="w-5 h-5 border rounded peer-checked:bg-blue-600 peer-checked:border-blue-600" />
<span class="peer-checked:line-through">Done</span>
</label>
The custom checkbox fills with blue when the hidden input is checked. The label text is struck through.
Dropdown.
<details class="group">
<summary class="cursor-pointer">Menu</summary>
<ul class="hidden group-open:block">
<li>Item 1</li>
<li>Item 2</li>
</ul>
</details>
The <details> is the group. The group-open: applies when the details is open. The list is hidden by default and shown when open.
Navigation with active state.
<nav class="group">
<a class="group-hover:text-blue-600">Link</a>
</nav>
The nav is the group. The link changes color when the nav is hovered.
Form with validation.
<input type="email" class="peer" required />
<p class="peer-invalid:text-red-500">Please enter a valid email</p>
The input is the peer. The message is shown in red when the input is invalid. The message can be hidden by default and shown with the variant.
Radio group.
<label>
<input type="radio" name="size" class="peer" />
<span class="peer-checked:bg-blue-600">Small</span>
</label>
The radio is the peer. The span is styled when the radio is checked.
Tooltip.
<div class="group relative">
<button>Hover me</button>
<div class="absolute hidden group-hover:block">Tooltip</div>
</div>
The tooltip is hidden by default and shown when the parent is hovered.
Toggle switch.
<label class="flex items-center">
<input type="checkbox" class="peer sr-only" />
<div class="w-10 h-5 bg-gray-300 rounded-full peer-checked:bg-blue-600">
<div class="w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5" />
</div>
</label>
The toggle switch background and knob move when the checkbox is checked.
Image with overlay.
<div class="group relative">
<img src="image.jpg" />
<div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity">
<p>Overlay text</p>
</div>
</div>
The overlay fades in when the parent is hovered.
Complete Example Session
<!-- ============================================ -->
<!-- PART 1: BASIC GROUP HOVER -->
<!-- ============================================ -->
<div class="group">
<h2 class="group-hover:text-blue-600">Title</h2>
</div>
<!-- ============================================ -->
<!-- PART 2: CARD WITH HOVER -->
<!-- ============================================ -->
<div class="group rounded-lg border p-4 hover:shadow-lg">
<h3 class="group-hover:text-blue-600">Card Title</h3>
<p class="text-gray-500">Description</p>
</div>
<!-- ============================================ -->
<!-- PART 3: NAMED GROUP -->
<!-- ============================================ -->
<div class="group/card">
<div class="group/inner">
<p class="group-hover/card:text-blue-600">Card hover</p>
<p class="group-hover/inner:text-red-600">Inner hover</p>
</div>
</div>
<!-- ============================================ -->
<!-- PART 4: BASIC PEER -->
<!-- ============================================ -->
<input type="email" class="peer" />
<p class="peer-invalid:text-red-500">Invalid email</p>
<!-- ============================================ -->
<!-- PART 5: FLOATING LABEL -->
<!-- ============================================ -->
<div class="relative">
<input class="peer w-full border rounded px-3 py-2 placeholder-transparent" placeholder="Email" />
<label class="absolute left-3 top-2 transition-all peer-placeholder-shown:top-2 peer-focus:-top-3 peer-focus:text-xs">
Email
</label>
</div>
<!-- ============================================ -->
<!-- PART 6: CUSTOM CHECKBOX -->
<!-- ============================================ -->
<label class="flex items-center gap-2">
<input type="checkbox" class="peer sr-only" />
<span class="w-5 h-5 border rounded peer-checked:bg-blue-600 peer-checked:border-blue-600" />
<span class="peer-checked:line-through">Done</span>
</label>
<!-- ============================================ -->
<!-- PART 7: DROPDOWN WITH DETAILS -->
<!-- ============================================ -->
<details class="group">
<summary class="cursor-pointer">Menu</summary>
<ul class="hidden group-open:block">
<li>Item 1</li>
<li>Item 2</li>
</ul>
</details>
<!-- ============================================ -->
<!-- PART 8: TOOLTIP -->
<!-- ============================================ -->
<div class="group relative">
<button>Hover me</button>
<div class="absolute hidden group-hover:block">Tooltip</div>
</div>
<!-- ============================================ -->
<!-- PART 9: TOGGLE SWITCH -->
<!-- ============================================ -->
<label class="flex items-center">
<input type="checkbox" class="peer sr-only" />
<div class="w-10 h-5 bg-gray-300 rounded-full peer-checked:bg-blue-600">
<div class="w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5" />
</div>
</label>
<!-- ============================================ -->
<!-- PART 10: NAMED PEER -->
<!-- ============================================ -->
<div>
<input class="peer/email" type="email" />
<input class="peer/password" type="password" />
<p class="peer-invalid/email:text-red-500">Invalid email</p>
<p class="peer-invalid/password:text-red-500">Invalid password</p>
</div>
The ten parts covered basic group-hover, a card with hover, a named group, basic peer, the floating label, the custom checkbox, the dropdown with <details>, the tooltip, the toggle switch, and the named peer.
Quick Reference
Group Variants
| Variant | Applies When Parent Is |
|---|---|
group-hover: | Hovered |
group-focus: | Focused |
group-focus-within: | Contains focus |
group-active: | Active |
group-disabled: | Disabled |
group-checked: | Checked |
group-open: | Open |
group-invalid: | Invalid |
Peer Variants
| Variant | Applies When Peer Is |
|---|---|
peer-hover: | Hovered |
peer-focus: | Focused |
peer-focus-visible: | Focus-visible |
peer-active: | Active |
peer-disabled: | Disabled |
peer-checked: | Checked |
peer-invalid: | Invalid |
peer-valid: | Valid |
peer-placeholder-shown: | Showing placeholder |
peer-required: | Required |
Naming
| Syntax | Purpose |
|---|---|
group | Unnamed group |
group/name | Named group |
group-hover/name: | Target named group |
peer | Unnamed peer |
peer/name | Named peer |
peer-focus/name: | Target named peer |
Rules
| Rule | Requirement |
|---|---|
| Group | Parent of the target |
| Peer | Before the target in the DOM |
| Same parent | Group and target share a parent |
| Same parent | Peer and target share a parent |
| Nesting | Group variants work through nesting |
| Composition | Group and peer can combine |
Best Practices
✅ Do This:
<!-- Mark the parent with group -->
<div class="group">
<p class="group-hover:text-blue-600">Title</p>
</div> <!-- ✅ -->
<!-- Use peer for siblings -->
<input class="peer" />
<p class="peer-focus:text-blue-600">Label</p> <!-- ✅ -->
<!-- Name the groups when nested -->
<div class="group/card">
<div class="group/inner">
<p class="group-hover/card:text-blue-600">Card</p>
</div>
</div> <!-- ✅ -->
<!-- Use peer-checked for custom controls -->
<input type="checkbox" class="peer sr-only" />
<span class="peer-checked:bg-blue-600" /> <!-- ✅ -->
<!-- Use peer-placeholder-shown for floating labels -->
<input class="peer placeholder-transparent" />
<label class="peer-placeholder-shown:top-2 peer-focus:-top-3"> <!-- ✅ -->
</label>
<!-- Combine group and peer -->
<div class="group">
<input class="peer" />
<span class="peer-focus:text-blue-600 group-hover:text-gray-700"> <!-- ✅ -->
</div>
❌ Don’t Do This:
<!-- Don't use group-hover: without the group class -->
<div>
<p class="group-hover:text-blue-600"> <!-- no group --> <!-- ❌ -->
</div>
<!-- Don't put the peer after the target -->
<span class="peer-focus:text-blue-600">Label</span>
<input class="peer" /> <!-- wrong order --> <!-- ❌ -->
<!-- Don't use group and peer on the same element -->
<div class="group peer"> <!-- confusing --> <!-- ⚠️ -->
<!-- Don't forget that the peer must share the parent -->
<div>
<input class="peer" />
</div>
<p class="peer-focus:text-blue-600"> <!-- different parent --> <!-- ❌ -->
</p>
<!-- Don't use named groups without the name -->
<div class="group/card">
<p class="group-hover:text-blue-600"> <!-- unnamed --> <!-- ⚠️ -->
</p>
</div>
<!-- Don't forget the sr-only for custom controls -->
<input type="checkbox" class="peer" />
<!-- the native checkbox is still visible --> <!-- ⚠️ -->
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Group hover no effect | Missing group class | Add group to the parent |
| Peer no effect | Peer after target | Move peer before |
| Wrong group targeted | Nested groups | Use named groups |
| Custom checkbox not working | Missing sr-only | Hide the native input |
| Floating label stuck | Missing placeholder-transparent | Add the class |
| Peer in different parent | Sibling rule | Same parent |
| Group and peer conflict | Both on same element | Use one |
Real-World Examples
1. Card Hover
<div class="group hover:shadow-lg">
<h3 class="group-hover:text-blue-600">Title</h3>
</div>
2. Floating Label
<input class="peer placeholder-transparent" />
<label class="peer-focus:-top-3 peer-placeholder-shown:top-2">Email</label>
3. Custom Checkbox
<input type="checkbox" class="peer sr-only" />
<span class="peer-checked:bg-blue-600" />
4. Dropdown
<details class="group">
<summary>Menu</summary>
<ul class="hidden group-open:block">...</ul>
</details>
5. Tooltip
<div class="group">
<button>Hover</button>
<div class="hidden group-hover:block">Tooltip</div>
</div>
6. Toggle Switch
<input type="checkbox" class="peer sr-only" />
<div class="peer-checked:bg-blue-600">
<div class="peer-checked:translate-x-5" />
</div>
7. Form Validation
<input class="peer" required />
<p class="peer-invalid:text-red-500">Invalid</p>
8. Image Overlay
<div class="group">
<img />
<div class="opacity-0 group-hover:opacity-100">Overlay</div>
</div>
9. Named Group
<div class="group/card">
<p class="group-hover/card:text-blue-600">Card</p>
</div>
10. Named Peer
<input class="peer/email" />
<p class="peer-invalid/email:text-red-500">Invalid</p>
Visual
Group Variant
┌─────────────────────────────────────────────────────────────┐
│ <div class="group"> │
│ <h2 class="group-hover:text-blue-600">Title</h2> │
│ </div> │
│ │
│ Parent hovered? │
│ │ │
│ ├── NO ──▶ Title is default color │
│ │ │
│ └── YES ──▶ Title is blue │
│ │
│ The child reacts to the parent's state. │
│ │
└─────────────────────────────────────────────────────────────┘
Peer Variant
┌─────────────────────────────────────────────────────────────┐
│ <input class="peer" /> │
│ <p class="peer-focus:text-blue-600">Label</p> │
│ │
│ Input focused? │
│ │ │
│ ├── NO ──▶ Label is default color │
│ │ │
│ └── YES ──▶ Label is blue │
│ │
│ The sibling reacts to the earlier sibling's state. │
│ The peer must come first in the DOM. │
│ │
└─────────────────────────────────────────────────────────────┘
Named Groups
┌─────────────────────────────────────────────────────────────┐
│ <div class="group/card"> │
│ <div class="group/inner"> │
│ <p class="group-hover/card:text-blue-600">Card</p> │
│ <p class="group-hover/inner:text-red-600">Inner</p> │
│ </div> │
│ </div> │
│ │
│ Hover the card: │
│ - "Card" turns blue (card is hovered) │
│ - "Inner" turns red (inner is also hovered) │
│ │
│ Hover the inner: │
│ - "Card" stays default │
│ - "Inner" turns red │
│ │
│ The names disambiguate the target. │
│ │
└─────────────────────────────────────────────────────────────┘
Floating Label
┌─────────────────────────────────────────────────────────────┐
│ EMPTY (placeholder shown) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Email │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ peer-placeholder-shown:top-2 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ FOCUSED │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Email │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ peer-focus:-top-3 peer-focus:text-xs │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ The label moves up when the input is focused. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Group class | group on the parent |
| Group variant | group-*: on the child |
| Peer class | peer on the sibling |
| Peer variant | peer-*: on the later sibling |
| Named group | group/name and group-*:/name: |
| Named peer | peer/name and peer-*:/name: |
| Peer order | Peer before target |
| Composition | Group and peer can combine |
| Common group | group-hover:, group-open: |
| Common peer | peer-focus:, peer-checked:, peer-invalid: |
Key takeaways:
- The
groupclass marks a parent and thegroup-*variant styles a child based on the parent’s state. The hover, focus, or other state is on the parent, and the child reacts. The child can be any descendant, not just a direct child. - The
peerclass marks a sibling and thepeer-*variant styles a later sibling based on the earlier sibling’s state. The peer must come before the target in the DOM. The two must share a parent. - The named forms disambiguate nested groups and multiple peers.
group/cardandgroup-hover/card:target a specific group.peer/emailandpeer-invalid/email:target a specific peer. - The group and peer variants work with any state variant.
group-hover:,group-focus:,group-open:,group-disabled:,peer-focus:,peer-checked:,peer-invalid:, and the rest. - The peer variant is the standard for the floating label and the custom control. The
peer-placeholder-shown:andpeer-focus:move the label. Thepeer-checked:styles the custom checkbox or toggle. - The group variant is the standard for the card hover and the dropdown. The
group-hover:scales the image and changes the title. Thegroup-open:shows the dropdown list. - The two variants compose. A child can react to the parent’s hover and the sibling’s focus at the same time. The two are independent, and both can apply.
Remember: The group-* and peer-* variants are the tools for the styling that depends on another element’s state. The group is the parent, the peer is the sibling, and the variant is the child’s reaction. The state is the browser’s pseudo-class, and the browser applies it. No JavaScript is required for the parent’s hover to change the child’s color, or for the input’s focus to move the label. The patterns—card hover, floating label, custom checkbox, dropdown, tooltip—are the ones that appear in every application. Use the named forms for the nested cases, and the variants compose with the rest of the system.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!