</>
tw
Tailwind CSS v4 · Varianten · Selektoren · group · peer · has
Tailwind CSS v4 Varianten und Selektoren
group, peer, has, not und @custom-variant

Varianten sind das Herzstück des Tailwind-Ansatzes. Tailwind CSS v4 erweitert sie erheblich: benannte Gruppen, has:-Selektor, verbesserte peer-Logik und @custom-variant für projektspezifische Zustände – alles ohne eine einzige eigene CSS-Datei zu schreiben.

14 Min. Lesezeit hover · focus · group · peer · has · not · dark · @custom-variant Tailwind CSS v4.x · Oxide Compiler

1. Was Tailwind-Varianten sind und wie sie funktionieren

In Tailwind CSS ist eine Variante ein Präfix, das vor einer Utility-Klasse steht und definiert, unter welcher Bedingung diese Klasse angewendet wird. hover:bg-sky-500 bedeutet: den Hintergrund auf Sky-500 setzen, aber nur wenn das Element gehoverd wird. Intern generiert Tailwind dafür den CSS-Selektor .hover\:bg-sky-500:hover { background-color: ... }. Der entscheidende Unterschied zu traditionellem CSS: Der Entwickler schreibt den Zustand direkt im HTML auf das Element, statt separate CSS-Regeln für jeden Zustand zu schreiben.

In Tailwind CSS v4 ist das Varianten-System durch den Oxide Compiler effizienter und durch neue CSS-Features mächtiger geworden. Varianten können beliebig kombiniert werden: dark:hover:focus:bg-sky-600 ist ein gültiger Ausdruck, auch wenn er selten sinnvoll ist. Die Kombination dark:md:hover:bg-sky-600 – Dark Mode, ab Breakpoint md, beim Hover – ist ein häufig genutztes Muster für responsive Dark-Mode-Komponenten. Die Verarbeitungsreihenfolge der Varianten entspricht der Reihenfolge der Präfixe von außen nach innen: zuerst wird der Dark-Mode-Context geprüft, dann der Breakpoint, dann der Hover-Status.

2. Zustands-Varianten: hover, focus, active, disabled

Die Zustands-Varianten in Tailwind bilden CSS-Pseudo-Klassen ab. hover: generiert :hover, focus: generiert :focus, active: generiert :active, disabled: generiert :disabled. Für barrierefreiheitsorientiertes Styling besonders wichtig: focus-visible: statt focus:. Der Unterschied: :focus-visible zeigt den Fokus-Ring nur bei Tastaturnavigation, nicht bei Mausklick. Das entspricht den aktuellen Accessibility-Empfehlungen für Custom-Focus-Styles.

Formular-bezogene Zustands-Varianten sind in v4 vollständig: checked:, indeterminate:, placeholder-shown:, required:, valid:, invalid:, in-range:, out-of-range:, read-only:. Diese Varianten ermöglichen vollständige Formular-Validierungs-Visualisierung ohne JavaScript – ein Fehler-State für ein ungültiges Eingabefeld ist mit invalid:border-red-500 invalid:bg-red-50 direkt im HTML definiert. Die placeholder:-Variante spricht den Placeholder-Text an: placeholder:text-slate-400.


<!-- State variants for a complete form input with validation states -->
<div class="space-y-2">
  <label class="block text-sm font-semibold text-slate-700" for="email">
    E-Mail-Adresse
  </label>
  <input
    id="email"
    type="email"
    required
    class="
      w-full rounded-xl border border-slate-300 px-4 py-2.5 text-slate-900
      placeholder:text-slate-400
      focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 focus-visible:border-sky-500
      disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-slate-50
      invalid:border-red-400 invalid:bg-red-50 invalid:text-red-900
      invalid:focus-visible:ring-red-400
      valid:border-green-400 valid:bg-green-50
      transition-colors duration-200
    "
    placeholder="name@beispiel.de"
  >
  <!-- Error message — shown only when input is invalid (CSS :invalid state) -->
  <p class="text-sm text-red-600 hidden peer-invalid:block">
    Bitte eine gültige E-Mail-Adresse eingeben.
  </p>
</div>

3. Responsive Varianten: sm, md, lg, xl, 2xl

Responsive Varianten in Tailwind folgen dem Mobile-first-Prinzip: Eine Klasse ohne Präfix gilt für alle Bildschirmgrößen, ein Präfix wie md: gilt ab dem definierten Breakpoint aufwärts. Die Standard-Breakpoints in Tailwind v4 sind: sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px). In v4 können Breakpoints in @theme { --breakpoint-xs: 475px; } erweitert oder überschrieben werden.

Ein häufiges Missverständnis bei responsiven Varianten: Die Klasse md:hidden versteckt ein Element ab dem md-Breakpoint – nicht darunter. Um ein Element nur auf kleinen Bildschirmen zu verstecken, braucht man hidden md:block: versteckt standardmäßig, als Block-Element ab md. Für ausschließliche Breiten-Ranges gibt es keine eingebauten max-*-Varianten in v4, aber man kann max-md: als Präfix nutzen: max-md:flex-col setzt flex-direction auf column nur unterhalb des md-Breakpoints. Diese max-*-Varianten sind in Tailwind v4 standardmäßig verfügbar.

4. group und group-hover: Eltern-Kind-Interaktionen

Die group-Variante löst eine fundamentale CSS-Limitation: Kindelemente können in reinem CSS nicht auf den Zustand eines Elternelements reagieren. group auf dem Elternelement kombiniert mit group-hover: auf einem Kindelement generiert den Selektor .group:hover .group-hover\:bg-sky-50. Das Kindelement reagiert, wenn das Elternelement gehoverd wird – ohne JavaScript-EventListener. In Tailwind v4 sind alle Zustands-Varianten als group-Varianten verfügbar: group-focus:, group-active:, group-disabled:, group-checked:.

Neu in Tailwind v4: Benannte group-Instanzen. In v3 konnte man nur eine group-Ebene nutzen – bei verschachtelten Gruppen reagierte group-hover: auf die innerste Gruppe. In v4 gibt man Gruppen Namen: group/card auf dem äußeren Container und group/button auf einem inneren Container. Kindelemente können dann spezifisch auf eine bestimmte Gruppe reagieren: group-hover/card:bg-sky-50 reagiert nur, wenn der card-Container gehoverd wird, nicht wenn der button-Container gehoverd wird. Das macht komplexe verschachtelte Hover-Interaktionen ohne JavaScript möglich.


<!-- Named group instances — Tailwind v4 feature -->
<div class="group/card rounded-2xl border border-slate-200 p-6 hover:border-sky-300 hover:shadow-lg transition-all duration-200">
  <div class="flex items-start justify-between mb-4">
    <div>
      <h3 class="font-bold text-slate-900 group-hover/card:text-sky-700 transition-colors">
        Produkttitel
      </h3>
      <p class="text-sm text-slate-500 group-hover/card:text-slate-600 transition-colors">
        Kategorie · Brand
      </p>
    </div>

    <!-- Nested interactive element with its own group -->
    <div class="group/btn relative">
      <button class="
        size-10 rounded-lg bg-slate-100
        group-hover/card:bg-sky-100
        group-hover/btn:bg-sky-500
        flex items-center justify-center transition-colors duration-200
      ">
        <!-- Icon reacts to button hover (group/btn), not card hover (group/card) -->
        <svg class="w-5 h-5 text-slate-400 group-hover/btn:text-white transition-colors" 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>
      </button>
      <!-- Tooltip reacts only to btn hover -->
      <span class="absolute -top-9 left-1/2 -translate-x-1/2 bg-slate-900 text-white text-xs px-2 py-1 rounded opacity-0 group-hover/btn:opacity-100 transition-opacity whitespace-nowrap pointer-events-none">
        Zu Favoriten
      </span>
    </div>
  </div>
</div>

5. peer: Geschwister-Selektoren ohne JavaScript

Die peer-Variante nutzt den CSS-Geschwister-Selektor ~. Ein Element mit der peer-Klasse wird zum Referenz-Element. Alle nachfolgenden Geschwister können mit peer-*:-Präfixen auf den Zustand des peer-Elements reagieren. Das klassischste Beispiel: ein Checkbox-Zustand steuert die Darstellung eines nachfolgenden Labels oder weiteren Inhalts ohne JavaScript. peer-checked:bg-sky-500 auf einem Element nach einem Checkbox-peer färbt den Hintergrund, wenn die Checkbox aktiviert ist.

In Tailwind v4 sind peer-Instanzen ebenfalls benennbar: peer/newsletter als Klasse auf einem Input, dann peer-checked/newsletter:opacity-100 auf einem nachfolgenden Element. Das ermöglicht mehrere unabhängige peer-Beziehungen auf derselben Ebene. Wichtige Einschränkung: peer-Selektoren wirken nur in Vorwärtsrichtung – das peer-Element muss im DOM vor dem reagierenden Element stehen. Das liegt an der CSS-Spezifikation des ~-Selektors, nicht an Tailwind. Wer auf ein vorheriges Element reagieren will, muss die DOM-Reihenfolge anpassen oder auf JavaScript zurückgreifen.

6. has und not: moderne CSS-Selektoren in Tailwind v4

Tailwind v4 unterstützt native CSS-Selektoren wie :has() und :not() als Varianten. Die has:-Variante ist besonders mächtig, weil sie erstmals einen echten Eltern-Selektor in CSS ermöglicht: Ein Element kann basierend auf seinen Kindelementen gestylt werden. has-[input:checked]:bg-sky-50 auf einem Container färbt den Hintergrund, wenn irgendein darin enthaltenes Checkbox-Input aktiviert ist. In Formularen, Listenelements und interaktiven Karten ist das ein häufig benötigtes Muster.

Die not:-Variante wendet eine Klasse an, wenn ein Element einen Selektor nicht erfüllt. not-last:border-b setzt einen unteren Rand für alle Elemente außer dem letzten – ein sehr häufiges Muster für Listentrennlinien, das bisher border-b last:border-b-0 erforderte. Mit not-last:border-b ist der Ausdruck semantisch korrekt und braucht keine Überschreibung im letzten Element. Ebenso: not-disabled:hover:bg-sky-50 aktiviert den Hover-Effekt nur für Elemente, die nicht disabled sind – eine elegantere Alternative zu manueller Kombinationslogik in JavaScript.


<!-- has: variant — parent reacts to children state (Tailwind v4) -->

<!-- Form row: highlight when any child input is focused -->
<div class="has-[input:focus]:bg-sky-50 has-[input:focus]:ring-1 has-[input:focus]:ring-sky-200 rounded-lg p-4 transition-all">
  <label class="block text-sm font-semibold text-slate-700 mb-1">Firmenname</label>
  <input type="text" class="w-full border-0 bg-transparent focus:outline-none text-slate-900 placeholder:text-slate-400" placeholder="Musterag GmbH">
</div>

<!-- not-last: variant — border-b on all items except the last -->
<ul class="divide-y-0">
  <li class="not-last:border-b not-last:border-slate-200 px-4 py-3 flex items-center gap-3">
    <span class="size-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold">A</span>
    <span class="text-slate-800 font-medium">Erster Eintrag</span>
  </li>
  <li class="not-last:border-b not-last:border-slate-200 px-4 py-3 flex items-center gap-3">
    <span class="size-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold">B</span>
    <span class="text-slate-800 font-medium">Zweiter Eintrag</span>
  </li>
  <li class="not-last:border-b not-last:border-slate-200 px-4 py-3 flex items-center gap-3">
    <span class="size-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold">C</span>
    <span class="text-slate-800 font-medium">Letzter Eintrag — kein border-b</span>
  </li>
</ul>

7. dark und forced-colors: erscheinungsbild-Varianten

Die dark:-Variante ist eine der meistgenutzten Varianten in modernen Web-Projekten. In Tailwind v4 kann sie auf zwei Arten konfiguriert werden: media-based (Standard) reagiert auf das System-Erscheinungsbild des Benutzers via @media (prefers-color-scheme: dark), selector-based reagiert auf eine CSS-Klasse am HTML-Element, etwa dark. Für Projekte, die einen manuellen Dark-Mode-Toggle anbieten, ist selector-based die richtige Wahl. Die Konfiguration in v4 erfolgt über @custom-variant dark (&:where(.dark, .dark *)) im CSS.

Neu in Tailwind v4: forced-colors:-Variante. Sie reagiert auf den Windows-Hochkontrastmodus (@media (forced-colors: active)). Elemente, die im Hochkontrastmodus noch erkennbar und verwendbar sein müssen, können mit forced-colors:outline forced-colors:outline-current explizit für diesen Modus gestylt werden. Das ist eine Accessibility-Anforderung für öffentliche Web-Anwendungen nach WCAG 2.2 und EN 301 549. Tailwind macht dieses bisher aufwendige Styling durch eine einfache Variante zugänglich, ohne eigene @media forced-colors-Blöcke schreiben zu müssen.

8. Varianten kombinieren: was geht, was nicht

Varianten in Tailwind CSS v4 sind frei kombinierbar. Die Reihenfolge der Präfixe entspricht der Verschachtelung der generierten CSS-Selektoren: dark:md:hover:bg-sky-600 generiert den Selektor, der im Dark Mode, ab md-Breakpoint, beim Hover gilt. Theoretisch sind beliebig viele Kombinationen möglich. Praktisch sinnvoll sind Kombinationen bis zu drei Ebenen. Mehr Ebenen produzieren zwar valides CSS, machen aber HTML-Klassen schwer lesbar und sind oft ein Zeichen, dass die Komponente zu viel Logik im Template hat.

Variante Generierter Selektor Verwendungsfall
hover:bg-sky-500 .hover\:bg-sky-500:hover Einfacher Hover-Effekt
group-hover/card:opacity-100 .group\/card:hover .group-hover\/card\:opacity-100 Benannte Gruppe, Eltern-Hover
dark:md:hover:bg-slate-700 @media (prefers-color-scheme: dark) { @media (min-width: 768px) { &:hover } } Dark Mode + responsive + hover
has-[input:checked]:bg-sky-50 .has-[input:checked]\:bg-sky-50:has(input:checked) Eltern reagiert auf Kind-Zustand
peer-invalid:text-red-600 .peer:invalid ~ .peer-invalid\:text-red-600 Geschwister-Selektor rückwärts

9. @custom-variant: eigene Varianten definieren

@custom-variant ist die v4-Methode, um eigene Varianten zu definieren, ohne JavaScript-Plugins schreiben zu müssen. In v3 war das über addVariant('name', selector) in der Konfiguration möglich – in v4 geschieht es direkt im CSS. Die Syntax ist @custom-variant name { selector-expression; }. Das ermöglicht projektspezifische Varianten für Zustände, die Tailwind nicht standardmäßig abdeckt: Theme-Klassen am Body-Element, Zustandsklassen aus JavaScript-Frameworks, Data-Attribute-basierte Zustände.

Konkrete Beispiele für nützliche @custom-variant-Definitionen: Eine theme-blue:-Variante reagiert auf data-theme="blue" am Root-Element – damit können komplette Farbthemes durch ein einzelnes Attribut gesteuert werden. Eine js-loaded:-Variante reagiert auf eine Klasse, die JavaScript nach dem Laden setzt – nützlich für Progressive-Enhancement-Patterns, bei denen bestimmte Styles nur mit JavaScript sinnvoll sind. Eine print:-Variante für @media print ist in Tailwind v4 eingebaut, zeigt aber die Flexibilität des Systems.


/* tailwind.css — Custom variants for project-specific states */
@import "tailwindcss";

/* Dark mode via selector (for manual toggle support) */
@custom-variant dark (&:where(.dark, .dark *));

/* Theme variants based on data attribute */
@custom-variant theme-blue (&:where([data-theme="blue"], [data-theme="blue"] *));
@custom-variant theme-green (&:where([data-theme="green"], [data-theme="green"] *));

/* Progressive enhancement — styles active only after JS loads */
@custom-variant js-ready (&:where(.js-loaded, .js-loaded *));

/* Reduced motion accessibility variant */
@custom-variant motion-safe {
  @media (prefers-reduced-motion: no-preference) {
    &:where(*) { @slot; }
  }
}

/* Magento-specific: active navigation item */
@custom-variant nav-active (&:where(.active, .current));

/*
  Usage in HTML:
  <body data-theme="blue">
    <nav>
      <a class="nav-active:font-bold nav-active:text-sky-600 theme-blue:text-blue-600" href="/">Home</a>
    </nav>
  </body>
*/

10. Zusammenfassung

Tailwind CSS v4 Varianten und Selektoren decken das gesamte Spektrum moderner CSS-Zustandssteuerung ab – von einfachem hover bis zu verschachtelten benannten Gruppen und nativen has:-Selektoren. Das Ergebnis: UI-Interaktionen wie Hover-Effekte, Formular-Validierung, Dark-Mode-Umschaltung und theme-basiertes Styling sind vollständig ohne JavaScript-EventListener realisierbar. Das reduziert JavaScript-Abhängigkeiten, verbessert die Performance und vereinfacht die Wartung.

Für Projekte, die auf Tailwind v4 setzen, lohnt sich die Investition in das Verständnis von group, peer, has: und @custom-variant besonders. Diese vier Konzepte lösen die Mehrheit der Fälle, in denen bisher JavaScript-EventListener für rein visuelle Zustandsänderungen eingesetzt wurden. @custom-variant macht das Variantensystem extensibel für jeden Projekt-Kontext – ob Magento-Theme-Klassen, Alpine.js-Zustände oder Data-Attribute aus einem Content-Management-System.

Tailwind CSS v4 Varianten — Das Wichtigste auf einen Blick

group und peer

In v4 mit Namen verwendbar: group/card und group-hover/card:. Ermöglicht mehrere unabhängige Gruppen in verschachtelten Layouts ohne JavaScript.

has: und not:

Echte Eltern-Selektoren: has-[input:checked]:bg-sky-50. not-last:border-b ersetzt das border-b-last:border-b-0-Pattern. Native CSS, kein Hack.

@custom-variant

Eigene Varianten im CSS definieren – kein JavaScript-Plugin mehr nötig. Für Theme-Klassen, Data-Attribute und Framework-Zustände.

Kombinierbarkeit

Varianten frei kombinierbar: dark:md:hover:bg-sky-600. Reihenfolge = Verschachtelung der Selektoren. Bis drei Ebenen in der Praxis sinnvoll.

11. FAQ: Tailwind CSS v4 Varianten und Selektoren

1Was ist eine Tailwind Variante?
Ein Präfix, das definiert, unter welcher Bedingung eine Klasse angewendet wird. hover:bg-sky-500 generiert intern .hover\:bg-sky-500:hover { ... }.
2Neu bei group in v4?
Benannte Gruppen: group/card und group-hover/card:. Mehrere verschachtelte Gruppen unabhängig voneinander steuerbar – kein JavaScript nötig.
3Wie funktioniert has: in v4?
Bildet natives CSS :has() ab. has-[input:checked]:bg-sky-50: Container reagiert auf aktivierte Checkbox darin. Echter Eltern-Selektor in CSS.
4focus: vs. focus-visible:?
focus: immer bei Fokus. focus-visible: nur bei Tastaturnavigation. Für Custom-Focus-Styles nach WCAG-Empfehlungen immer focus-visible: verwenden.
5Eigene Varianten in v4?
Mit @custom-variant im CSS. Kein JavaScript-Plugin nötig. Für Theme-Klassen, Data-Attribute und Framework-Zustände.
6peer nur vorwärts?
Ja. CSS ~ Selektor funktioniert nur für nachfolgende Geschwister. Das peer-Element muss im DOM vor dem reagierenden Element stehen.
7Varianten kombinieren?
dark:md:hover:bg-sky-600 ist valide. Bis drei Ebenen in der Praxis sinnvoll. Reihenfolge = Verschachtelung der generierten Selektoren.
8forced-colors: Variante?
Reagiert auf Windows-Hochkontrastmodus. Für Accessibility nach WCAG 2.2. Explizites Styling für Hochkontrast ohne eigene @media-Blöcke.
9Dark Mode mit manuellem Toggle in v4?
@custom-variant dark (&:where(.dark, .dark *)) im CSS. JavaScript setzt Klasse .dark am HTML-Element. dark: reagiert dann auf Klasse statt System-Setting.
10not-last: und not-first: nützlich?
not-last:border-b ersetzt border-b last:border-b-0. not-first:mt-4 ersetzt mt-4 first:mt-0. Semantisch präziser, kürzerer HTML-Code.