Tailwind CSS Academy · 강의

그룹 및 피어 변형

그룹 및 피어 패턴을 사용해 부모 요소나 형제 요소의 상태에 따라 자식 요소를 스타일링합니다.

레슨 4/413개 단계

그룹 및 피어 변형은(는) CoddyKit의 무료 Tailwind CSS Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Tailwind CSS Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Tailwind CSS Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

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.

무료로 시작

AI 튜터와 함께 HTML을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
30
레슨
120

자주 묻는 질문

“그룹 및 피어 변형” 강의는 무료인가요?

네 — “그룹 및 피어 변형” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Tailwind CSS Academy 강의 전체를 잠금 해제할 수 있습니다. Tailwind CSS Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“그룹 및 피어 변형”에서 뭘 배우나요?

그룹 및 피어 패턴을 사용해 부모 요소나 형제 요소의 상태에 따라 자식 요소를 스타일링합니다. 브라우저에서 직접 실행하는 실습 코드로 Tailwind CSS Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Tailwind CSS Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Tailwind CSS Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“그룹 및 피어 변형” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Tailwind CSS Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Tailwind CSS Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Hover 및 활성 상태 변형
  2. Focus 및 Focus-Visible 변형
  3. 비활성화 및 자리 표시자 변형
  4. 그룹 및 피어 변형
← Tailwind CSS Academy(으)로 돌아가기