</>
tw
Tailwind CSS · CSS :has() · Eltern-Selektor · Tailwind v4
Tailwind has:
Die CSS :has() Variante für kontextabhängige Styles

CSS :has() ist der erste echte Eltern-Selektor in der Geschichte von CSS. In Tailwind v4 nutzt du ihn direkt im Markup: has-[:checked], has-[input:focus], has-[.error]. Das ermöglicht Styles, die auf den Zustand von Kindselementen reagieren – ohne JavaScript und ohne eigene CSS-Datei.

13 Min. Lesezeit has-[:checked] · has-[input:focus] · has-[.error] · group-has Tailwind CSS v4 · Chrome 105+ · Firefox 121+ · Safari 15.4+

1. Was CSS :has() grundlegend anders macht

Seit den Anfängen von CSS gab es eine fundamentale Einschränkung: CSS kann nur vorwärts selektieren. Ein Selektor wie div p wählt p-Elemente innerhalb eines div, aber niemals ein div basierend darauf, was es enthält. Jahrzehntelang mussten Entwickler für solche Anforderungen JavaScript einsetzen – einen Event-Listener setzen, den DOM traversieren und Klassen manuell hinzufügen. CSS :has() bricht diese Beschränkung auf und ist damit der erste echte Eltern-Selektor in der Geschichte der Sprache.

In Tailwind v4 ist diese Fähigkeit als Tailwind has-Variante direkt im Utility-System verfügbar. has-[:checked] auf einem Container-Element greift, wenn irgendein Nachfahre des Containers den :checked-Zustand hat. has-[input:focus] greift, wenn ein input-Element im Container fokussiert ist. has-[.error] greift, wenn ein Element mit der Klasse error im Container existiert. Diese Tailwind has-Klassen generieren exakt das CSS :has()-Konstrukt im Hintergrund – ohne dass man eine CSS-Datei anfassen muss.

2. Syntax der Tailwind has-Variante

Die Tailwind has-Variante folgt demselben Muster wie andere Tailwind-Modifikatoren: has-[SELEKTOR]:UTILITY. Der Selektor in eckigen Klammern ist ein beliebiger CSS-Selektor, der auf einen Nachfahren des Elements angewendet wird. has-[:checked]:bg-sky-50 setzt den Hintergrund auf sky-50, wenn das Element eine angehakte Checkbox als Nachfahren hat. has-[input:focus]:ring-2 fügt einen Ring hinzu, wenn ein input fokussiert ist. Die Syntax ist flexibel: Typ-Selektoren, Klassen-Selektoren, Attribut-Selektoren und Pseudo-Klassen sind alle möglich.

Kombinationen mit anderen Modifikatoren funktionieren ebenfalls: dark:has-[:checked]:bg-slate-800 für Dark-Mode, sm:has-[img]:grid-cols-2 für responsive bedingte Layouts. In Tailwind v4 kann man die has-Variante auch mit beliebigen Selektoren über die Arbitrary-Syntax erweitern: has-[.product-card:hover] oder has-[input[type='radio']:checked]. Das Ergebnis ist ein vollständig deklarativer Ansatz für CSS, der ohne eine einzige Zeile JavaScript auskommt.


<!-- has-variant syntax examples in Tailwind CSS -->

<!-- Container highlights when any child input is focused -->
<div class="border border-slate-200 rounded-xl p-4
            has-[input:focus]:border-sky-500
            has-[input:focus]:ring-2
            has-[input:focus]:ring-sky-100
            transition-all duration-200">
  <input type="text" placeholder="Suche..." class="w-full outline-none text-sm text-slate-700">
</div>

<!-- Card changes style when internal checkbox is checked -->
<label class="block border-2 border-slate-200 rounded-2xl p-4 cursor-pointer
              has-[:checked]:border-sky-500
              has-[:checked]:bg-sky-50
              transition-all duration-200">
  <input type="radio" name="plan" value="pro" class="sr-only">
  <span class="font-semibold text-slate-800 has-[:checked]:text-sky-700">Pro Plan</span>
  <p class="text-sm text-slate-500 mt-1">Unbegrenzte Projekte, Priority Support</p>
</label>

<!-- Form wrapper that reacts to validation state -->
<div class="has-[:invalid]:border-red-300 has-[:invalid]:bg-red-50
            has-[:valid]:border-emerald-300 has-[:valid]:bg-emerald-50
            border-2 rounded-xl p-4 transition-colors duration-200">
  <input type="email" required class="w-full outline-none text-sm" placeholder="E-Mail">
</div>

3. has-[:checked]: Formulare und Toggle-UI

Das wohl häufigste Einsatzszenario für Tailwind has ist die Reaktion auf Checkbox- und Radio-Zustände. Das Muster has-[:checked] auf einem Container reagiert darauf, wenn irgendeins seiner Kind-Inputs angehakt oder ausgewählt ist. Das ist leistungsfähiger als der peer-checked-Ansatz, weil has keine bestimmte DOM-Reihenfolge erfordert und tief verschachtelte Inputs erkennt.

Für Pricing-Tables und Optionsauswahl-UIs ist has-[:checked] in Tailwind das perfekte Werkzeug: Jede Optionskarte wird mit einem versteckten Radio-Input versehen. Die Karte selbst bekommt has-[:checked]:ring-2 has-[:checked]:ring-sky-500 has-[:checked]:bg-sky-50. Wenn der Nutzer auf die Karte klickt, wechselt sie visuell in den Ausgewählt-Zustand. Dabei kann das Label (der klickbare Bereich) die gesamte Karte sein – kein JavaScript für Event-Handling, kein Alpine.js-x-model. Die Browser-Formularlogik übernimmt alles.


<!-- Pricing plan selector using has-[:checked] — pure CSS -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">

  <!-- Starter plan -->
  <label class="relative block border-2 border-slate-200 rounded-2xl p-6 cursor-pointer
                has-[:checked]:border-sky-500 has-[:checked]:bg-sky-50 has-[:checked]:shadow-lg
                hover:border-slate-300 transition-all duration-200">
    <input type="radio" name="pricing" value="starter" class="sr-only" checked>
    <!-- Checkmark badge: only visible when selected -->
    <div class="absolute top-3 right-3 w-6 h-6 rounded-full bg-sky-500 flex items-center justify-center
                opacity-0 has-[:checked]:opacity-100 transition-opacity">
      <svg class="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/>
      </svg>
    </div>
    <p class="font-bold text-slate-800 text-lg mb-1">Starter</p>
    <p class="text-3xl font-bold text-slate-900 mb-3">€ 0 <span class="text-sm font-normal text-slate-500">/Monat</span></p>
    <ul class="text-sm text-slate-600 space-y-1">
      <li>1 Projekt</li>
      <li>5 GB Speicher</li>
      <li>Community Support</li>
    </ul>
  </label>

  <!-- Pro plan -->
  <label class="relative block border-2 border-slate-200 rounded-2xl p-6 cursor-pointer
                has-[:checked]:border-sky-500 has-[:checked]:bg-sky-50 has-[:checked]:shadow-lg
                hover:border-slate-300 transition-all duration-200">
    <input type="radio" name="pricing" value="pro" class="sr-only">
    <p class="font-bold text-slate-800 text-lg mb-1">Pro</p>
    <p class="text-3xl font-bold text-slate-900 mb-3">€ 29 <span class="text-sm font-normal text-slate-500">/Monat</span></p>
    <ul class="text-sm text-slate-600 space-y-1">
      <li>10 Projekte</li>
      <li>50 GB Speicher</li>
      <li>E-Mail Support</li>
    </ul>
  </label>

  <!-- Enterprise plan -->
  <label class="relative block border-2 border-slate-200 rounded-2xl p-6 cursor-pointer
                has-[:checked]:border-sky-500 has-[:checked]:bg-sky-50 has-[:checked]:shadow-lg
                hover:border-slate-300 transition-all duration-200">
    <input type="radio" name="pricing" value="enterprise" class="sr-only">
    <p class="font-bold text-slate-800 text-lg mb-1">Enterprise</p>
    <p class="text-3xl font-bold text-slate-900 mb-3">€ 99 <span class="text-sm font-normal text-slate-500">/Monat</span></p>
    <ul class="text-sm text-slate-600 space-y-1">
      <li>Unbegrenzte Projekte</li>
      <li>1 TB Speicher</li>
      <li>Priority Support</li>
    </ul>
  </label>

</div>

4. has-[input:focus]: Fokus-Zustände auf Wrappern

Ein klassisches Problem in der Formulargestaltung: Man möchte den gesamten Wrapper um ein Eingabefeld hervorheben, wenn das Feld fokussiert ist – nicht nur das Feld selbst. Mit :focus-within war das früher möglich, aber das greift für jedes fokussierbare Element innerhalb des Wrappers. Tailwind has bietet mehr Präzision: has-[input:focus] greift nur, wenn speziell ein input-Element fokussiert ist, nicht ein Button oder ein Link im selben Container.

Das ist besonders wertvoll für Suchfelder mit Icons und Shortcut-Anzeigen: Der gesamte Such-Wrapper soll sich hervorheben, wenn der Nutzer in das Suchfeld klickt. Mit has-[input:focus]:border-sky-500 has-[input:focus]:shadow-md auf dem Wrapper passiert genau das – ohne JavaScript, ohne onfocus-Event, ohne Alpine.js. Das Icon im Wrapper kann mit group-has-[input:focus] ebenfalls seinen Zustand wechseln, wenn man den Wrapper zusätzlich als group markiert.

5. has-[.klasse]: Komponentenzustände steuern

Tailwind has kann nicht nur auf Pseudoklassen wie :checked oder :focus reagieren, sondern auf beliebige CSS-Selektoren – einschließlich Klassen-Selektoren. has-[.error] auf einem Formular-Wrapper greift, wenn irgendein Kind-Element die Klasse error hat. Das ist besonders nützlich für Komponentensysteme, wo JavaScript (oder Alpine.js) Klassen setzt und das Layout darauf reagieren soll.

Das Muster: Alpine.js setzt die Klasse error auf ein Validierungs-Element, das im DOM vorhanden ist, wenn ein Fehler aufgetreten ist. Der übergeordnete Formular-Container hat has-[.error]:border-red-300 has-[.error]:bg-red-50. Er reagiert automatisch, ohne dass Alpine.js den Container direkt ansprechen muss. Dieses Muster trennt Verantwortlichkeiten: JavaScript verwaltet den Zustand (Klasse setzen/entfernen), CSS reagiert auf den Zustand (Styles anwenden). Die Tailwind has-Variante ist dabei die Brücke zwischen beiden Welten.


<!-- has-[.klasse] reacts to dynamic class from Alpine.js -->
<div x-data="{ hasError: false, value: '' }"
     class="border-2 rounded-2xl p-6 transition-all duration-200
            has-[.field-error]:border-red-300
            has-[.field-error]:bg-red-50
            has-[.field-success]:border-emerald-300
            has-[.field-success]:bg-emerald-50">

  <label class="block text-sm font-semibold text-slate-700 mb-2">Benutzername</label>

  <input type="text"
    x-model="value"
    @input="hasError = value.length > 0 && value.length < 3"
    class="w-full border border-slate-300 rounded-xl px-4 py-2.5 text-sm outline-none
           focus:border-sky-400 focus:ring-2 focus:ring-sky-100">

  <!-- Error element: class field-error triggers has-[.field-error] on parent -->
  <p x-show="hasError && value.length > 0"
     class="field-error mt-2 text-xs text-red-600 font-medium">
    Mindestens 3 Zeichen erforderlich.
  </p>

  <!-- Success element: class field-success triggers has-[.field-success] -->
  <p x-show="!hasError && value.length >= 3"
     class="field-success mt-2 text-xs text-emerald-600 font-medium">
    Benutzername ist verfügbar.
  </p>
</div>

6. Bedingte Layouts mit has in Tailwind

Eine der überraschendsten Anwendungen der Tailwind has-Variante ist die bedingte Layout-Steuerung. Ein Grid-Container kann sein Layout wechseln, wenn er ein bestimmtes Kind-Element enthält: has-[img]:grid-cols-[1fr_2fr] schaltet auf ein zweispaltiges Layout um, wenn das Element ein Bild enthält. has-[aside]:grid-cols-[280px_1fr] fügt eine Sidebar-Spalte hinzu, wenn ein aside-Element vorhanden ist.

Dieses Muster ermöglicht flexible CMS-Layouts, bei denen Redakteure entscheiden, ob ein Bild oder eine Sidebar eingebunden wird – und das Layout passt sich automatisch an, ohne dass Templates angepasst werden müssen. In Magento-Hyvä-Layouts ist das besonders interessant: Ein Produkt-Container kann automatisch ein anderes Layout annehmen, wenn die Produktgalerie oder die Konfigurations-Optionen im DOM vorhanden sind. Die Tailwind has-Variante reagiert auf den DOM-Zustand, nicht auf eine explizite Klasse.

7. not-has: Negation des Eltern-Selektors

Tailwind unterstützt neben has-[...] auch die Negation: not-has-[...] oder über die Kombination has-[]:not-[...]. Das ist nützlich für Styles, die nur dann gelten sollen, wenn kein bestimmtes Kind vorhanden ist. Ein leerer Zustand – ein Container ohne Listenelemente – kann mit not-has-[li]:flex not-has-[li]:items-center not-has-[li]:justify-center als zentrierter Leerbereich dargestellt werden, der sich automatisch in ein normales Layout verwandelt, sobald Elemente hinzukommen.

In Tailwind v4 schreibt man die Negation als [&:not(:has([...]))]:utility oder nutzt die native not-has-Variante, falls im Projekt konfiguriert. Das ist ein fortgeschrittenes Muster, aber für CMS-Inhalte und dynamische Listen extrem hilfreich: Der "Keine Ergebnisse"-Zustand und der normale Listen-Zustand unterscheiden sich durch Klassen auf demselben Container – ohne JavaScript, das den Zustand ermitteln und Klassen setzen muss. Die Tailwind has-Variante macht den DOM-Zustand selbst zur Wahrheitsquelle für das Styling.

8. group-has und peer-has als Kompositions-Pattern

Tailwind has lässt sich mit group und peer kombinieren, um Styles auf beliebige Elemente im Komponentenbaum zu übertragen. Das Muster group-has-[input:checked]:... auf einem Kind-Element reagiert darauf, ob das nächste übergeordnete group-Element irgendwo einen angehakten Input enthält. Das ist mächtiger als group-has allein, weil der zu stylende Knoten beliebig tief im Kind-Baum liegen kann.

Ein Beispiel: Eine komplexe Formularkarte, bei der ein Bestätigungs-Text am unteren Ende erscheinen soll, wenn irgendwo in der Karte eine bestimmte Option ausgewählt ist. Der Bestätigungs-Text liegt in keiner direkten Geschwister-Beziehung zum Radio-Input (kein peer möglich) und ist auch kein direktes Kind (kein einfaches has). Mit group auf der Karte und group-has-[:checked]:block auf dem Bestätigungs-Text löst man das sauber: Die Karte beobachtet als Gruppe ihren gesamten Kindsbaum, und der Bestätigungs-Text reagiert auf den Gruppen-Zustand.


<!-- group + has combination: complex multi-level state propagation -->
<div class="group border border-slate-200 rounded-2xl overflow-hidden">

  <div class="p-6">
    <h3 class="font-bold text-slate-800 mb-4">Lieferoption wählen</h3>

    <div class="space-y-3">
      <label class="flex items-center gap-3 cursor-pointer">
        <input type="radio" name="shipping" value="standard" class="peer accent-sky-500">
        <span class="text-sm text-slate-700 peer-checked:font-semibold peer-checked:text-sky-700">
          Standardversand (3–5 Tage) — kostenlos
        </span>
      </label>
      <label class="flex items-center gap-3 cursor-pointer">
        <input type="radio" name="shipping" value="express" class="peer accent-sky-500">
        <span class="text-sm text-slate-700 peer-checked:font-semibold peer-checked:text-sky-700">
          Expressversand (1–2 Tage) — € 4,99
        </span>
      </label>
      <label class="flex items-center gap-3 cursor-pointer">
        <input type="radio" name="shipping" value="same-day" class="peer accent-sky-500">
        <span class="text-sm text-slate-700 peer-checked:font-semibold peer-checked:text-sky-700">
          Same-Day-Lieferung — € 12,99
        </span>
      </label>
    </div>
  </div>

  <!-- This confirmation bar is hidden until ANY radio in the group is checked -->
  <!-- group-has-[:checked] watches the entire group's subtree -->
  <div class="hidden group-has-[:checked]:flex items-center gap-3 px-6 py-3 bg-sky-50 border-t border-sky-100">
    <svg class="w-4 h-4 text-sky-600 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
    </svg>
    <p class="text-sm text-sky-700 font-medium">Lieferoption ausgewählt. Weiter zum nächsten Schritt.</p>
  </div>

</div>

9. has vs. group vs. peer im direkten Vergleich

Die drei Tailwind-Mechanismen für zustandsbasierte Styles ergänzen sich, haben aber unterschiedliche Stärken. Die Wahl hängt von der DOM-Struktur und der Richtung der Zustandspropagation ab.

Mechanismus Selektor-Typ DOM-Anforderung Typisches Einsatzszenario
has-[...] Eltern reagiert auf Kind Kein – Kind kann beliebig tief sein Container auf Checkbox, Input-Fokus, Fehlerklasse reagieren
group-hover Eltern → Kind (Pseudo-Zustand des Elterns) group auf Elternelement setzen Hover-Kaskaden, Overlay bei Karten, Navigationsmenüs
peer-checked Geschwister → nachfolgendes Geschwister Peer-Ziel muss nach Peer im HTML stehen Checkbox als Toggle, Floating Labels, Akkordeons
group-has Eltern beobachtet Kind, steuert beliebiges Kind group auf Container, group-has-[...] auf Ziel Bestätigungs-Elemente, die nicht direkte Geschwister sind
not-has-[...] Eltern reagiert auf Abwesenheit eines Kinds Kein Leerzustände, fallback-Layouts ohne bestimmte Elemente

Die Faustregel für die Wahl: has-[...] direkt auf einem Element ist die erste Wahl, wenn das Element selbst seinen Stil basierend auf dem Zustand eines Kindes wechseln soll. group-has kommt zum Einsatz, wenn das zu stylierende Element weit vom beobachteten Element entfernt ist und man über einen gemeinsamen Eltern-Container kommunizieren muss. peer-checked bleibt die einfachste Option für direkte Geschwister-Beziehungen.

Mironsoft

Tailwind CSS v4 · Hyvä Themes · Alpine.js

Moderne CSS-Features, richtig eingesetzt?

Wir nutzen Tailwind has, group und peer konsequent, um interaktive Interfaces zu bauen, die minimal auf JavaScript angewiesen sind – schneller, robuster, einfacher zu warten.

has-Analyse

Bestehende JavaScript-Zustände auf has/group/peer-Eignung prüfen

Formular-UI

Formulare mit has-Validierung, Floating Labels und CSS-only Feedback bauen

Magento-Hyvä

Produktseiten und Checkout-UI mit Tailwind has optimieren

10. Zusammenfassung

Die Tailwind has-Variante bringt CSS :has() in das Utility-First-System und macht damit den ersten echten Eltern-Selektor der CSS-Geschichte für Tailwind-Entwickler zugänglich. has-[:checked] für Optionskarten und Pricing-Tables, has-[input:focus] für Wrapper-Hervorhebungen, has-[.error] für die Reaktion auf Alpine.js-gesetzte Klassen – all das funktioniert ohne JavaScript, ohne Event-Listener und ohne manuelle DOM-Manipulation. Die DOM-Struktur selbst ist die Wahrheitsquelle.

Im Zusammenspiel mit group-has und peer-has entstehen Kompositions-Patterns, die komplexe Zustandspropagation über beliebige DOM-Tiefen ermöglichen. Der praktische Vorteil: Weniger JavaScript, kleinere Bundles, bessere Performance. Gleichzeitig bleibt die Deklarativität des Tailwind-Ansatzes erhalten – kein dynamisches Klassen-Setzen in JavaScript, kein Auseinanderdriften von Styles und Logik. Die Tailwind has-Variante ist eines der wichtigsten Features von Tailwind v4 und wird in modernen Projekten schnell unverzichtbar.

Tailwind has — Das Wichtigste auf einen Blick

Syntax

has-[SELEKTOR]:UTILITY. Jeder CSS-Selektor in eckigen Klammern: has-[:checked], has-[input:focus], has-[.error], has-[img].

Wann has statt peer?

has wenn das Element sich selbst stylen soll. peer wenn ein nachfolgendes Geschwister gesteuert werden soll. group-has für tief verschachtelte Ziele.

Browser-Unterstützung

Chrome 105+, Firefox 121+, Safari 15.4+. Alle modernen Browser seit 2022/2023. Für Legacy-Support auf group/peer zurückgreifen.

Kombination

Kombinierbar mit dark:, sm:, lg: und anderen Modifikatoren. Auch group-has-[...] und peer-has-[...] für Cross-Element-Kommunikation verfügbar.

11. FAQ: Tailwind has und CSS :has() Variante

1Was ist die Tailwind has-Variante?
CSS :has() als Tailwind-Utility: has-[:checked]:bg-sky-50 setzt Hintergrund wenn ein angehakter Input als Nachfahre existiert. Erster echter Eltern-Selektor in CSS.
2Browser-Unterstützung für :has()?
Chrome 105+ (Aug 2022), Firefox 121+ (Dez 2023), Safari 15.4+ (Mrz 2022). Alle modernen Evergreen-Browser.
3has statt peer-checked: Wann?
has wenn das Element sich selbst stylen und der Input beliebig tief sein kann. peer-checked wenn ein direktes nachfolgendes Geschwister gesteuert wird.
4has-[.klasse] für dynamische Zustände?
JS/Alpine.js setzt Klasse auf Kind. Container hat has-[.error]:border-red-300. Trennt Zustandsverwaltung (JS) von Darstellung (CSS) sauber.
5Was ist group-has?
group-has-[...] auf einem Kind: reagiert wenn das übergeordnete group-Element einen bestimmten Nachfahren enthält. Für weit entfernte Ziele ohne Geschwister-Beziehung.
6has mit anderen Modifikatoren kombinieren?
Ja: dark:has-[:checked]:bg-slate-800, sm:has-[img]:grid-cols-2, lg:has-[aside]:grid-cols-[280px_1fr]. Alle Tailwind-Modifikatoren kombinierbar.
7Pricing-Table mit has-[:checked] bauen?
label-Element als Karte mit sr-only radio input. Karte: has-[:checked]:ring-2 has-[:checked]:bg-sky-50. Klick auf Karte → Radio gecheckt → Karte markiert. Kein JS.
8not-has in Tailwind?
not-has-[li]:flex not-has-[li]:justify-center zeigt zentrierten Leerbereich wenn keine Listenelemente vorhanden sind – umgekehrt zum normalen Layout.
9has ersetzt focus-within?
focus-within greift für jedes fokussierbare Element. has-[input:focus] nur für input-Elemente – präziser und kontrollierter.
10has vs. group: Unterschied?
has styliert das Element selbst basierend auf Nachfahren-Zustand. group styliert Kinder basierend auf dem Zustand des Elterns selbst. Gegensätzliche Richtungen.