</>
tw
Tailwind CSS · group · peer · CSS-only Interaktionen
Tailwind group und peer:
Selektoren für interaktive UI ohne JavaScript

Hover-Kaskaden, Formularvalidierung und komplexe UI-Zustände – mit Tailwinds group und peer Modifikatoren lassen sich Interaktionen beschreiben, für die andere Projekte Alpine.js oder React anwerfen. Das Ergebnis: schlankes HTML, kein JavaScript-Overhead.

12 Min. Lesezeit group-hover · peer-checked · group-has · group-focus-within Tailwind CSS v4 · CSS :has() · Moderne Browser

1. Das Konzept hinter group und peer

Tailwind CSS group und peer sind Modifikator-Klassen, die CSS-Selektoren aus der natürlichen Browser-Kaskade zugänglich machen, ohne dass man eigenes CSS schreiben muss. Das Grundprinzip: Man markiert ein Elternelement mit der Klasse group und kann dann in Kindelementen Klassen wie group-hover:opacity-100 schreiben. Diese Klasse greift genau dann, wenn das übergeordnete Element den Hover-Zustand hat. Das entspricht dem CSS-Selektor .group:hover .child – Tailwind generiert genau das, man schreibt es aber direkt am Kind.

Der Unterschied zwischen Tailwind group und Tailwind peer liegt in der Richtung: group wirkt von Eltern auf Kinder (Abwärts-Beziehung), während peer von einem Geschwisterelement auf das folgende Geschwister wirkt (Vorwärts-Beziehung im Sinne des CSS Adjacent Sibling Selectors). Das Peer-Element bekommt die Klasse peer, und das nachfolgende Geschwisterelement verwendet Klassen wie peer-checked:block. Wichtig: Das Peer-Ziel muss nach dem Peer-Element im HTML stehen – CSS kann nicht rückwärts selektieren.

2. group-hover: Hover-Kaskaden über Komponenten

Das häufigste Einsatzszenario für Tailwind group ist die Hover-Kaskade über Karten-Komponenten. Ohne group muss man für jedes Kindelement, das beim Hover der Karte animiert werden soll, JavaScript auf das Hover-Event der Elternkarte setzen. Mit Tailwind group schreibt man stattdessen auf der Karte group und auf jedem betroffenen Kindelement die entsprechende group-hover:-Klasse: group-hover:translate-y-0, group-hover:opacity-100, group-hover:text-sky-600. Der Browser übernimmt die gesamte Logik über seinen nativen CSS-Selektor.

Das Ergebnis sind Interaktionen, die frame-perfect smooth sind – ohne JavaScript-Event-Listener, ohne requestAnimationFrame, ohne Alpine.js-Boilerplate. Kombiniert mit transition-all duration-300 auf den Kindelementen entstehen flüssige Animationen. Das Pattern ist besonders wirkungsvoll bei Produktkarten in E-Commerce-Systemen: Bild-Zoom, Overlay einblenden, Quick-Add-Button erscheinen lassen – alles über group-hover Tailwind-Klassen.


<!-- Product card with group-hover cascade — no JavaScript needed -->
<div class="group relative overflow-hidden rounded-2xl bg-white border border-slate-200 shadow-sm hover:shadow-xl transition-shadow duration-300 cursor-pointer">

  <!-- Image with zoom effect on parent hover -->
  <div class="relative overflow-hidden aspect-square bg-slate-100">
    <img src="/product.jpg" alt="Produkt"
      class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105">

    <!-- Overlay appears on hover -->
    <div class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
      <button class="bg-white text-slate-900 font-bold text-sm px-5 py-2.5 rounded-xl
        translate-y-4 group-hover:translate-y-0 transition-transform duration-300 shadow-lg">
        Zum Warenkorb
      </button>
    </div>
  </div>

  <!-- Text content with animated color change -->
  <div class="p-4">
    <h3 class="font-semibold text-slate-800 group-hover:text-sky-700 transition-colors duration-200">
      Produktname
    </h3>
    <p class="text-sm text-slate-500 mt-1 group-hover:text-slate-700 transition-colors duration-200">
      Kurze Beschreibung
    </p>
    <div class="flex items-center justify-between mt-3">
      <span class="text-lg font-bold text-slate-900">€ 49,99</span>
      <!-- Arrow icon slides in on hover -->
      <svg class="w-5 h-5 text-slate-300 group-hover:text-sky-500 group-hover:translate-x-1 transition-all duration-200"
        fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
      </svg>
    </div>
  </div>
</div>

3. Benannte Gruppen für verschachtelte Strukturen

Sobald mehrere Gruppen verschachtelt werden – eine Karte mit interaktiven Buttons, die selbst wieder Hover-Effekte auslösen sollen – entstehen Konflikte: Jede group-hover:-Klasse greift auf die nächste übergeordnete group. In Tailwind CSS lässt sich das mit benannten Gruppen auflösen. Man schreibt group/card auf die äußere Karte und group/button auf den inneren Button. Die Kindelemente verwenden dann gezielt group-hover/card: oder group-hover/button:.

Benannte Tailwind group Modifikatoren sind besonders wertvoll in Navigationsmenüs mit Untermenüs: Die Hauptnavigation ist group/nav, jedes Hauptmenü-Item ist group/item. Das Untermenü öffnet sich mit group-hover/item:block, und Hover-Effekte innerhalb des Untermenüs referenzieren group/item separat von group/nav. Das ergibt ein vollständig funktionierendes Dropdown-Menü ohne eine Zeile JavaScript.

4. peer-checked: Checkboxen und Radios als UI-Controller

Das mächtigste Einsatzszenario für Tailwind peer ist die Nutzung von Checkboxen und Radio-Buttons als CSS-only UI-Controller. Das Muster: Ein <input type="checkbox"> mit der Klasse peer wird im HTML vor dem Element platziert, das gesteuert werden soll. Das gesteuerte Element bekommt peer-checked:block hidden – es ist standardmäßig versteckt und erscheint, wenn die Checkbox angehakt ist. Zusammen mit sr-only auf dem Input-Element entsteht ein vollständig CSS-gesteuertes Toggle-System.

Dieses peer-checked Tailwind-Muster ermöglicht unter anderem: Accordion-Komponenten ohne JavaScript, Tab-Navigationen mit Radio-Buttons, Dark-Mode-Toggles auf reiner CSS-Basis, benutzerdefinierte Checkbox-Designs und Sternebewertungs-Interfaces. Der Vorteil gegenüber Alpine.js: Kein JavaScript-Bundle, kein Flash of Unstyled Content, keine Hydration. Der Nachteil: Die Reihenfolge im HTML ist streng – das Peer-Ziel muss nach dem Peer-Element kommen.


<!-- CSS-only accordion using peer-checked — zero JavaScript -->
<div class="border border-slate-200 rounded-2xl overflow-hidden divide-y divide-slate-200">

  <!-- Accordion item 1 -->
  <div>
    <input type="checkbox" id="acc1" class="peer sr-only">
    <label for="acc1"
      class="flex items-center justify-between px-6 py-4 cursor-pointer font-semibold text-slate-800
             hover:bg-slate-50 transition-colors peer-checked:text-sky-700">
      Wie schnell werden Bestellungen versendet?
      <svg class="w-5 h-5 text-slate-400 transition-transform duration-300 peer-checked:rotate-180"
        fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
      </svg>
    </label>
    <!-- Content hidden by default, shown when checkbox is checked -->
    <div class="max-h-0 overflow-hidden transition-all duration-300 peer-checked:max-h-48">
      <div class="px-6 pb-4 text-slate-600 text-sm">
        Bestellungen werden werktags bis 14:00 Uhr noch am selben Tag versendet.
        Die Lieferzeit beträgt 1–3 Werktage innerhalb Deutschlands.
      </div>
    </div>
  </div>

  <!-- Accordion item 2 -->
  <div>
    <input type="checkbox" id="acc2" class="peer sr-only">
    <label for="acc2"
      class="flex items-center justify-between px-6 py-4 cursor-pointer font-semibold text-slate-800
             hover:bg-slate-50 transition-colors peer-checked:text-sky-700">
      Welche Zahlungsmethoden werden akzeptiert?
      <svg class="w-5 h-5 text-slate-400 transition-transform duration-300 peer-checked:rotate-180"
        fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
      </svg>
    </label>
    <div class="max-h-0 overflow-hidden transition-all duration-300 peer-checked:max-h-48">
      <div class="px-6 pb-4 text-slate-600 text-sm">
        Wir akzeptieren Kreditkarte, PayPal, SEPA-Lastschrift und Kauf auf Rechnung.
      </div>
    </div>
  </div>

</div>

5. peer-focus und peer-valid für Formularlogik

Tailwind peer kennt alle CSS-Pseudoklassen des Elements, auf das es angewendet wird. Für Formularfelder sind die wichtigsten: peer-focus, peer-valid, peer-invalid, peer-required und peer-placeholder-shown. Das klassische Floating-Label-Muster – ein Label, das im Eingabefeld sitzt und nach oben springt, sobald das Feld fokussiert oder ausgefüllt wird – lässt sich vollständig mit peer-focus und peer-placeholder-shown in Tailwind umsetzen.

Für Validierungs-Feedback kombiniert man peer-invalid mit peer-not-placeholder-shown: Die Fehlermeldung erscheint nur dann, wenn das Feld ungültig ist und der Nutzer bereits etwas eingegeben hat (das Placeholder ist also nicht mehr sichtbar). So verhindert man, dass alle Felder beim ersten Laden des Formulars sofort rot angezeigt werden. Das Gleiche gilt für Erfolgszustände mit peer-valid. In Kombination mit HTML5-Validierungsattributen (required, type="email", minlength) ergibt sich eine vollständige Client-seitige Formularvalidierung ohne JavaScript.


<!-- Floating label input with validation — no JavaScript -->
<div class="relative">
  <input
    id="email"
    type="email"
    required
    placeholder=" "
    class="peer w-full border border-slate-300 rounded-xl px-4 pt-6 pb-2 text-sm text-slate-800
           focus:outline-none focus:border-sky-500 focus:ring-2 focus:ring-sky-100
           invalid:[&:not(:placeholder-shown)]:border-red-400
           invalid:[&:not(:placeholder-shown)]:ring-red-100
           valid:[&:not(:placeholder-shown)]:border-emerald-400">

  <!-- Floating label: moves up when focused or filled -->
  <label for="email"
    class="absolute left-4 top-4 text-slate-400 text-sm pointer-events-none
           transition-all duration-200
           peer-focus:top-1.5 peer-focus:text-xs peer-focus:text-sky-600
           peer-[&:not(:placeholder-shown)]:top-1.5
           peer-[&:not(:placeholder-shown)]:text-xs">
    E-Mail-Adresse
  </label>

  <!-- Validation messages -->
  <p class="mt-1 text-xs text-red-500 hidden peer-invalid:[&:not(:placeholder-shown)~&]:block">
    Bitte gib eine gültige E-Mail-Adresse ein.
  </p>
  <p class="mt-1 text-xs text-emerald-600 hidden peer-valid:[&:not(:placeholder-shown)~&]:block">
    E-Mail-Adresse ist gültig.
  </p>
</div>

6. group-focus-within für Eingabefelder mit Overlay-Labels

group-focus-within ist die Variante von group-hover für den Fokus-Zustand: Die Gruppe reagiert, sobald irgendein Kindelement Fokus erhält. Das ist besonders nützlich für Eingabefelder mit umgebenden Icons oder Buttons: Wenn der Nutzer das Eingabefeld fokussiert, soll das gesamte Feld (einschließlich Icon-Wrapper) visuell hervorgehoben werden. Ohne group-focus-within müsste man JavaScript auf das focus-Event des Inputs setzen und manuell CSS-Klassen auf den Wrapper anwenden.

Das Muster in der Praxis: Ein div mit group umschließt ein Suchfeld-Icon und ein input. Der Wrapper bekommt group-focus-within:border-sky-500 group-focus-within:ring-2 group-focus-within:ring-sky-100. Das Icon bekommt group-focus-within:text-sky-500. Alles reagiert synchron auf das Fokussieren des Inputs. Kombiniert mit peer-Klassen für Validierungsfeedback entsteht ein vollständig interaktives Formular-Design, das ausschließlich auf CSS-Mechanismen basiert.

7. group-has und peer-has: der Vorwärts-Selektor

group-has und peer-has basieren auf dem modernen CSS-Selektor :has() und sind das mächtigste Feature in der Tailwind group/peer-Familie. Während group-hover auf den Zustand des Gruppenelements selbst reagiert, reagiert group-has darauf, ob das Gruppenelement ein bestimmtes Kind enthält. group-has-[input:checked] löst aus, wenn das Gruppenelement irgendwo in seiner Tiefe eine angehakte Checkbox enthält – unabhängig von der Verschachtelungstiefe.

Das ermöglicht Muster, die mit reinem CSS bisher unmöglich waren: Ein übergeordnetes Layout-Element ändert sich, wenn eine versteckte Checkbox in einem seiner Kinder angehakt wird. Oder eine Karte hebt sich hervor, wenn ein darin eingebettetes Radio-Button-Input ausgewählt ist – selbst wenn das Input mehrere Ebenen tief verschachtelt ist. In Tailwind v4 schreibt man das als group-has-[input:checked]:ring-2 auf dem gewünschten visuellen Element. Peer-has funktioniert analog für Geschwisterelemente, die auf den :has()-Zustand eines vorangehenden Peers reagieren.

8. Praxisbeispiel: Produktkarte mit mehreren Zuständen

Ein vollständiges Praxisbeispiel zeigt, wie sich Tailwind group und Tailwind peer kombinieren lassen. Die Anforderung: Eine Produktkarte, die beim Hover einen Quick-View-Button einblendet, beim Anklicken der Wunschlisten-Checkbox visuell in den "Gemerkt"-Zustand wechselt, und dem Nutzer dabei sofort Feedback gibt – ohne JavaScript. Die Checkbox steuert als Peer den gesamten visuellen Zustand der Karte: angehakter Herz-Button wird ausgefüllt, Karte bekommt einen blauen Rahmen, Badge erscheint.

Für dieses Muster verschachtelt man group und peer gezielt: Die Karte ist die group für alle Hover-Effekte. Die Wunschlisten-Checkbox ist der peer, der den Zustand der Karte und des Herz-Icons steuert. Das Herz-Icon ist gleichzeitig Peer-Ziel und Teil der Gruppe. Mit benannten Gruppen (group/card) und benannten Peers (peer/wishlist) bleibt die Zuständigkeit klar, auch wenn weitere Interaktionselemente hinzukommen.


<!-- Product card: group-hover + peer-checked for multiple states -->
<div class="group/card relative overflow-hidden rounded-2xl bg-white border border-slate-200
            peer-checked/wishlist:border-sky-400 peer-checked/wishlist:ring-2 peer-checked/wishlist:ring-sky-100
            shadow-sm hover:shadow-lg transition-all duration-300">

  <!-- Wishlist toggle — acts as peer for the card state -->
  <input type="checkbox" id="wl1" class="peer/wishlist sr-only">

  <!-- Image area -->
  <div class="relative aspect-square overflow-hidden bg-slate-50">
    <img src="/product.jpg" alt="Produkt" class="w-full h-full object-cover group-hover/card:scale-105 transition-transform duration-500">

    <!-- Wishlist button — changes on peer-checked -->
    <label for="wl1"
      class="absolute top-3 right-3 w-9 h-9 rounded-full bg-white/90 shadow-md flex items-center justify-center cursor-pointer
             hover:bg-white transition-colors duration-150">
      <!-- Outline heart: visible when NOT checked -->
      <svg class="w-5 h-5 text-slate-400 peer-checked/wishlist:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
      </svg>
      <!-- Filled heart: visible when checked -->
      <svg class="w-5 h-5 text-red-500 hidden peer-checked/wishlist:block" fill="currentColor" viewBox="0 0 24 24">
        <path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
      </svg>
    </label>

    <!-- Quick view button — slides up on group hover -->
    <div class="absolute inset-x-0 bottom-0 flex justify-center pb-3
                translate-y-full group-hover/card:translate-y-0 transition-transform duration-300">
      <button class="bg-white text-slate-800 font-semibold text-xs px-4 py-2 rounded-xl shadow-lg hover:bg-sky-600 hover:text-white transition-colors">
        Schnellansicht
      </button>
    </div>
  </div>

  <!-- Product info -->
  <div class="p-4">
    <p class="text-xs font-semibold text-sky-600 uppercase tracking-wider mb-1">Kategorie</p>
    <h3 class="font-semibold text-slate-800 group-hover/card:text-sky-700 transition-colors">Produktname</h3>
    <p class="text-lg font-bold text-slate-900 mt-2">€ 79,00</p>
  </div>
</div>

9. group und peer Patterns im Vergleich

Die Wahl zwischen Tailwind group, Tailwind peer und Alpine.js hängt von der Komplexität der Interaktion und den Browser-Anforderungen ab.

Interaktion Werkzeug Tailwind-Klasse JS nötig?
Karten-Overlay beim Hover group group-hover:opacity-100 Nein
Accordion auf/zu peer peer-checked:max-h-48 Nein
Floating Label peer peer-focus:top-1.5 peer-[&:not(:placeholder-shown)]:top-1.5 Nein
Karte als "gewählt" markieren group-has group-has-[input:checked]:ring-2 Nein
Komplexe Zustandsverwaltung Alpine.js x-data + :class Ja

Die Faustregel: Alles, was auf CSS-Pseudoklassen (:hover, :focus, :checked, :valid, :has()) basiert, lässt sich mit Tailwind group und peer lösen. Sobald man dynamischen Zustand braucht, der nicht über native HTML-Elemente ausgedrückt werden kann – beispielsweise ein öffnendes Modal nach einem API-Call –, ist Alpine.js der richtige Ergänzung. Beide Ansätze schließen sich nicht aus: Viele Interaktionen lassen sich zu einem großen Teil mit group und peer abdecken und nur für echte dynamische Logik mit Alpine.js ergänzen.

Mironsoft

Interaktive UI mit Tailwind CSS und Hyvä Themes

Interaktive UI ohne JavaScript-Overhead?

Wir nutzen Tailwind group und peer konsequent für alle CSS-lösbaren Interaktionen und sparen Alpine.js für Fälle, bei denen es wirklich nötig ist – weniger Bundle, schnellere Seiten.

UI-Audit

Bestehende Alpine.js-Interaktionen auf group/peer-Eignung prüfen und vereinfachen

Komponentenbau

Karten, Akkordeons, Formulare und Navigationen mit group/peer implementieren

Hyvä-Optimierung

Magento Hyvä-Komponenten mit minimalstem JavaScript-Einsatz implementieren

10. Zusammenfassung

Tailwind group und Tailwind peer sind zwei der mächtigsten Modifikatoren in Tailwind CSS, weil sie die volle Ausdruckskraft nativer CSS-Selektoren zugänglich machen – ohne eigenes CSS. group steuert Kindelemente basierend auf dem Zustand des Elternelements: ideal für Karten, Navigationen und Overlays. peer steuert Geschwisterelemente basierend auf dem Zustand eines vorangehenden Geschwisterelements: ideal für Checkboxen als Controller, Floating Labels und Validierungs-Feedback. Benannte Gruppen und Peers (group/name, peer/name) lösen Konflikte in verschachtelten Strukturen.

Die kombinierte Anwendung beider Modifikatoren deckt einen Großteil aller UI-Interaktionen ab, die in Webprojekten gebraucht werden. Hover-Kaskaden, Accordion, Floating Labels, Wunschlisten-Toggles, Tab-Navigationen – all das funktioniert CSS-only. Erst wenn Zustand aus API-Calls, Benutzeranmeldungen oder komplexer Logik kommt, ist Alpine.js die sinnvolle Ergänzung. Das Ergebnis sind leichtgewichtige, schnelle Interfaces ohne JavaScript-Boilerplate für Dinge, die der Browser nativ kann.

Tailwind group und peer — Das Wichtigste auf einen Blick

group

Elternelement mit group markieren. Kindelemente nutzen group-hover:, group-focus: usw. Benannte Gruppen für Verschachtelungen: group/card.

peer

Steuerndes Element bekommt peer. Nachfolgendes Geschwisterelement reagiert mit peer-checked:, peer-focus:, peer-valid:. Muss nach dem Peer im HTML stehen.

group-has / peer-has

Reagiert darauf, ob das Element ein bestimmtes Kind enthält – basiert auf CSS :has(). Ermöglicht Vorwärts-Selektion über beliebige Tiefe.

Wann Alpine.js nötig?

Nur bei dynamischem Zustand aus API-Calls, komplexer Logik oder wenn der Zustand nicht über native HTML-Elemente (Checkbox, Input) abgebildet werden kann.

11. FAQ: Tailwind group und peer Selektoren

1Unterschied zwischen group und peer?
group: Eltern → Kinder (group-hover: auf Kindelementen). peer: Geschwister → nachfolgendes Geschwister (peer-checked: auf dem Ziel-Element).
2Peer-Ziel muss nach Peer stehen – warum?
Basiert auf CSS ~ (Adjacent Sibling Selector), der nur vorwärts selektiert. Rückwärts-Selektion ist in CSS nicht möglich.
3Konflikte bei verschachtelten Gruppen lösen?
Benannte Gruppen: group/card, group/button. Kinder referenzieren gezielt group-hover/card: oder group-hover/button:.
4CSS-only Akkordeon mit peer-checked?
Checkbox peer sr-only vor dem Content. Content: max-h-0 peer-checked:max-h-48 overflow-hidden transition-all. Label als sichtbarer Toggle. Kein JavaScript.
5Was ist group-has?
Reagiert wenn das Gruppenelement einen bestimmten Nachkommen enthält – basiert auf CSS :has(). Ermöglicht Vorwärts-Selektion über beliebige Verschachtelungstiefe.
6Floating Labels mit Tailwind peer?
Input: peer + placeholder=" ". Label danach: peer-focus:top-1.5 peer-focus:text-xs peer-[&:not(:placeholder-shown)]:top-1.5.
7Wann Alpine.js statt group/peer?
Wenn Zustand aus API-Calls kommt, komplexe Logik nötig ist, oder native HTML-Zustände nicht ausreichen.
8group-hover mit focus und active nutzen?
Ja: group-focus, group-active, group-focus-within, group-focus-visible. Alle Pseudoklassen des Elternelements sind als group-Varianten verfügbar.
9group und peer im selben Element kombinieren?
Ja. Benannte Gruppen/Peers verwenden, um Zuständigkeiten klar zu trennen und unbeabsichtigte Überschneidungen zu vermeiden.
10Tab-Interface mit peer-checked bauen?
Radio-Input per Tab: peer/tab1 sr-only. Tab-Inhalt: hidden peer-checked/tab1:block. Label als Tab-Button. Immer genau ein Tab aktiv.