Group and Peer Variants
Use the group and peer patterns to style child elements based on the state of a parent or sibling element.
Group and Peer Variants is a free Tailwind CSS Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Tailwind CSS Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Styling Children Based on Parent State
A common UI challenge is needing to style a child element based on the state of its parent. In plain CSS, this requires the parent-child combinator and custom class names. Tailwind's group and group-* variants solve this cleanly: mark the parent with the group class and prefix any child utility with group-hover:, group-focus:, or other group variants to apply those styles when the parent enters that state.
<!-- Arrow icon appears only on card hover -->
<div class="group flex items-center gap-3 bg-white border rounded-xl p-4 hover:border-blue-500 cursor-pointer transition-colors">
<div class="flex-1">
<h3 class="font-semibold">Settings</h3>
<p class="text-sm text-gray-500">Manage your account</p>
</div>
<!-- Arrow is invisible by default, visible on group hover -->
<span class="text-gray-400 group-hover:text-blue-500 group-hover:translate-x-1 transition-all">
→
</span>
</div>How group Works
The group class on a parent element creates a grouping scope. All descendants can then reference this scope using group-{variant}: prefixes. The magic happens via Tailwind's generated CSS: group-hover:text-blue-500 compiles to .group:hover .group-hover\:text-blue-500. This means the child's style changes only when the ancestor with the group class is hovered — not when any ancestor is hovered.
<!-- How group generates CSS -->
<!-- The group class on parent enables hover detection -->
<div class="group">
<!-- group-hover: applies when the group div above is hovered -->
<h2 class="text-gray-700 group-hover:text-blue-600 transition-colors">
Title changes when parent is hovered
</h2>
<p class="text-gray-500 group-hover:text-gray-700 transition-colors">
Description also changes
</p>
</div>group-hover for Card Overlays
A popular use case for group-hover is revealing an overlay or action buttons on a card when the user hovers over the entire card, not just the button. By making the card a group and the overlay opacity-0 group-hover:opacity-100, the overlay appears precisely when the cursor enters the card boundary — creating an elegant reveal effect without JavaScript.
<!-- Image card with hover overlay revealed via group -->
<div class="group relative w-48 h-48 rounded-xl overflow-hidden cursor-pointer">
<!-- Background image -->
<div class="absolute inset-0 bg-gradient-to-br from-purple-500 to-pink-600"></div>
<!-- Overlay: invisible by default, visible on group hover -->
<div class="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<button class="bg-white text-gray-900 px-4 py-2 rounded-lg font-medium text-sm">
View Details
</button>
</div>
</div>Named Groups for Nested Scopes
When you have nested groups, Tailwind's named group feature prevents ambiguity. Apply group/name to mark a group with an identifier, and use group-hover/name: on children to reference that specific ancestor. Without named groups, group-hover: always references the nearest ancestor with the group class, which may not be what you want in deeply nested structures.
<!-- Named groups prevent ambiguity in nested structures -->
<div class="group/card border rounded-xl p-4 hover:shadow-md transition-shadow">
<h3 class="font-bold group-hover/card:text-blue-600 transition-colors">Card Title</h3>
<!-- Inner list item group -->
<ul class="mt-3 space-y-2">
<li class="group/item flex items-center gap-2 cursor-pointer rounded px-2 py-1 hover:bg-gray-50">
<span class="text-sm group-hover/item:font-medium">First item</span>
<span class="ml-auto text-gray-400 opacity-0 group-hover/item:opacity-100 transition-opacity">→</span>
</li>
<li class="group/item flex items-center gap-2 cursor-pointer rounded px-2 py-1 hover:bg-gray-50">
<span class="text-sm group-hover/item:font-medium">Second item</span>
<span class="ml-auto text-gray-400 opacity-0 group-hover/item:opacity-100 transition-opacity">→</span>
</li>
</ul>
</div>group-focus for Interactive Parents
group-focus: triggers when the parent receives keyboard focus. This is particularly useful for custom file upload components, color pickers, and any compound widget where the container is the focusable element but inner labels or icons need to change appearance. The result is an accessible compound widget that shows focus cues on all its parts simultaneously.
<!-- Custom file upload with group-focus styling -->
<label class="group flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl p-8 cursor-pointer hover:border-blue-400 focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/20 transition-all">
<input type="file" class="sr-only" />
<div class="text-4xl mb-3 group-hover:scale-110 transition-transform">📁</div>
<p class="font-medium text-gray-600 group-hover:text-blue-600 transition-colors">Click to upload</p>
<p class="text-sm text-gray-400 mt-1">or drag and drop</p>
</label>Peer Variant Introduction
While group works parent-to-child, peer works sibling-to-sibling. Mark one element with the peer class, then use peer-{state}: on a following sibling to style it based on the peer's state. Critically, peer selectors only work with following siblings in the DOM — the peer element must appear before the styled element in the HTML source order.
<!-- Checkbox peer controls a sibling label's appearance -->
<label class="flex items-center gap-3 cursor-pointer">
<!-- Peer: the checkbox -->
<input type="checkbox" class="peer sr-only" />
<!-- Custom checkbox visual -->
<div class="w-5 h-5 border-2 border-gray-400 rounded peer-checked:bg-blue-500 peer-checked:border-blue-500 flex items-center justify-center transition-all">
<svg class="w-3 h-3 text-white opacity-0 peer-checked:opacity-100 transition-opacity" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
</svg>
</div>
<!-- Label changes on peer check -->
<span class="text-gray-700 peer-checked:text-blue-600 peer-checked:font-medium transition-colors">
I agree to the terms
</span>
</label>peer for Form Validation Labels
The peer pattern shines for showing validation messages that react to the input's state. An input with the required attribute can be paired with a sibling error message using peer-invalid:visible. When the field is invalid (empty on submit or contains a malformed value), the sibling error message becomes visible — all without JavaScript event listeners.
<!-- Input with CSS-only validation message via peer -->
<div class="max-w-sm space-y-1">
<label class="block text-sm font-medium">Email</label>
<!-- peer: marks the input -->
<input
type="email"
required
placeholder="Enter valid email"
class="peer w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 invalid:border-red-400 invalid:ring-red-400/20"
/>
<!-- Sibling error: hidden until peer is invalid -->
<p class="hidden peer-invalid:flex text-sm text-red-500 gap-1 items-center">
⚠ Please enter a valid email address
</p>
</div>peer-focus for Floating Labels
The floating label pattern — where a placeholder rises to become a label when the input is focused — can be built with peer and peer-focus:. The input gets the peer class, and the label (appearing after the input in the DOM) uses peer-focus:-translate-y-6 peer-focus:text-xs peer-focus:text-blue-500 to float upward when the input is focused.
<!-- Floating label with peer-focus -->
<div class="relative max-w-sm">
<input
type="text"
placeholder=" "
class="peer w-full border border-gray-300 rounded-lg px-4 pt-6 pb-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<!-- Label floats when input is focused or has content -->
<label class="absolute left-4 top-4 text-gray-400 text-sm transition-all peer-focus:top-1.5 peer-focus:text-xs peer-focus:text-blue-500 peer-[:not(:placeholder-shown)]:top-1.5 peer-[:not(:placeholder-shown)]:text-xs pointer-events-none">
First Name
</label>
</div>Named Peers for Multiple Siblings
Like named groups, you can create named peers with peer/name and reference them with peer-checked/name:. This lets you have multiple peer elements on the same level and style different siblings based on different peers. Without named peers, peer-checked: would reference the most recent previous sibling with the peer class.
<!-- Named peers for radio-controlled tabs -->
<div class="space-y-2">
<!-- Radio inputs as peers -->
<input type="radio" name="tab" id="tab1" class="peer/tab1 sr-only" checked />
<input type="radio" name="tab" id="tab2" class="peer/tab2 sr-only" />
<!-- Tab labels react to their specific peer -->
<div class="flex border-b">
<label for="tab1" class="px-4 py-2 cursor-pointer border-b-2 border-transparent peer-checked/tab1:border-blue-500 peer-checked/tab1:text-blue-600 font-medium transition-colors">
Overview
</label>
<label for="tab2" class="px-4 py-2 cursor-pointer border-b-2 border-transparent peer-checked/tab2:border-blue-500 peer-checked/tab2:text-blue-600 font-medium transition-colors">
Settings
</label>
</div>
</div>group-checked for List Selection
Custom selection lists where clicking a row selects it benefit greatly from group combined with checkboxes. Mark each list row as a group, hide the actual checkbox input with sr-only, and use group-has-[:checked]: (Tailwind v3.4+) or JavaScript class toggling to change the row's background. This creates keyboard-accessible, visually rich selection lists without complex event handling.
<!-- Selectable list rows using group -->
<ul class="divide-y border rounded-xl overflow-hidden max-w-sm">
<li class="group flex items-center gap-3 px-4 py-3 has-[:checked]:bg-blue-50 has-[:checked]:border-l-4 has-[:checked]:border-l-blue-500 cursor-pointer transition-colors">
<input type="checkbox" class="w-4 h-4 text-blue-600 rounded" />
<span class="font-medium">Alice Johnson</span>
<span class="ml-auto text-sm text-gray-400">Admin</span>
</li>
<li class="group flex items-center gap-3 px-4 py-3 has-[:checked]:bg-blue-50 has-[:checked]:border-l-4 has-[:checked]:border-l-blue-500 cursor-pointer transition-colors">
<input type="checkbox" class="w-4 h-4 text-blue-600 rounded" />
<span class="font-medium">Bob Smith</span>
<span class="ml-auto text-sm text-gray-400">Editor</span>
</li>
</ul>Real Group and Peer Use Case
Here is a feature-rich pricing card that uses both group and highlighting. When you hover the card, the CTA button changes color (group-hover), and when checked as the recommended plan, the entire card updates its border and background. This combination of group, peer, and state variants creates interactive UI without any JavaScript.
<!-- Pricing card with group-hover on CTA -->
<div class="group bg-white border-2 border-gray-200 hover:border-blue-500 rounded-2xl p-6 max-w-xs transition-all duration-300 cursor-pointer">
<div class="mb-4">
<span class="text-sm font-medium text-blue-600 bg-blue-50 px-3 py-1 rounded-full">Popular</span>
</div>
<h3 class="text-xl font-bold">Pro Plan</h3>
<p class="text-4xl font-bold mt-2">$29<span class="text-base font-normal text-gray-500">/mo</span></p>
<ul class="mt-4 space-y-2 text-sm text-gray-600">
<li>✓ Unlimited projects</li>
<li>✓ Priority support</li>
<li>✓ Custom domain</li>
</ul>
<!-- CTA changes on card hover using group-hover -->
<button class="mt-6 w-full bg-gray-100 group-hover:bg-blue-600 group-hover:text-white text-gray-700 font-semibold py-2.5 rounded-xl transition-all duration-300">
Get Started
</button>
</div>Quick Check
Test your understanding of Tailwind CSS Mastery concepts from this lesson.
Lesson Recap
In this lesson you learned: group marks a parent and group-hover: / group-focus: style descendants based on the parent's state, peer marks a sibling and peer-hover: / peer-focus: / peer-checked: style following siblings based on the peer's state, and named groups and peers like group/card and peer/tab avoid ambiguity in nested or multiple-peer layouts. Next up we explore Tailwind's responsive breakpoint system.
Frequently asked questions
Is the “Group and Peer Variants” lesson free?
Yes — the full text of “Group and Peer Variants” is free to read here on the web, and the Tailwind CSS Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Tailwind CSS Academy course, upgrade to CoddyKit PRO.
What will I learn in “Group and Peer Variants”?
Use the group and peer patterns to style child elements based on the state of a parent or sibling element. You practise Tailwind CSS Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Tailwind CSS Academy?
No prior experience is required. Tailwind CSS Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Group and Peer Variants” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Tailwind CSS Academy lesson?
Yes. Every Tailwind CSS Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Hover and Active Variants
- Focus and Focus-Visible Variants
- Disabled and Placeholder Variants
- Group and Peer Variants