CSS :has() · Parent-Selektor · Conditional Styling · 2026
CSS :has() — der Game Changer für Layouts
Parent-Selektor, Conditional Styling und Formular-States

CSS :has() ist mehr als ein Parent-Selektor — es ist ein vollständiger Kontextselektor. Layout-Entscheidungen, die bisher JavaScript-Event-Handler erforderten, lassen sich mit :has() deklarativ im Stylesheet treffen. Das verändert, wie man CSS-Architekturen aufbaut.

12 Min. Lesezeit :has() · Parent-Selektor · Conditional Styling · Formular-States · Browser-Support Chrome 105+ · Firefox 121+ · Safari 15.4+

1. Was CSS :has() wirklich ist

CSS :has() wird oft als "der Parent-Selektor" bezeichnet, den Entwickler seit Jahren forderten. Das ist korrekt, aber nur die Hälfte der Geschichte. CSS :has() ist ein relationaler Pseudo-Klassen-Selektor, der ein Element selektiert, wenn es einen oder mehrere passende Nachfolger enthält. Das "passend" kann ein direktes Kind sein, ein beliebiger Nachfolger, oder auch ein Nachbar-Element in bestimmten Kombinationen. Die Macht liegt darin, dass CSS-Regeln auf ein Element angewendet werden können, das im DOM oberhalb oder neben dem Bedingungselement liegt — eine Fähigkeit, die CSS vorher vollständig fehlte.

Das revolutionäre an CSS :has(): Es verändert die Richtung der Selektion. Alle CSS-Selektoren vor CSS :has() arbeiteten vorwärts: Man selektiert ein Element, weil es einen bestimmten Vorfahren, einen bestimmten Typ oder einen bestimmten Zustand hat. CSS :has() erlaubt es, rückwärts zu selektieren: Man selektiert ein Element, weil seine Nachfolger oder Geschwister einen bestimmten Zustand haben. Eine Karte mit einem Bild soll anders gestylt werden als eine Karte ohne Bild? Mit CSS :has() reicht eine CSS-Regel: .card:has(img). Ein Formular, das ein ungültiges Pflichtfeld enthält, soll seinen Submit-Button deaktivieren? form:has(:invalid) button[type=submit].

Vor CSS :has() erforderten solche Bedingungen JavaScript: Event-Listener auf Zustandsänderungen, DOM-Traversal aufwärts, Class-Toggling. Jede dieser JavaScript-Lösungen hat eine Latenz — sie reagiert auf Ereignisse, nicht gleichzeitig mit dem Browser-Layout-Prozess. CSS :has() ist Teil des Style-Recalculation-Prozesses und hat keine Latenz.

2. Syntax und Grundregeln

Die Syntax von CSS :has() ist schlicht: Der Selektor, der die Bedingung ausdrückt, steht als Argument in den Klammern. div:has(p) selektiert alle div-Elemente, die mindestens einen p-Nachfolger enthalten. div:has(> p) selektiert nur div-Elemente mit einem direkten p-Kind. div:has(+ p) selektiert ein div, das von einem p-Geschwister gefolgt wird — das ist der Geschwister-Kontext, der mit CSS :has() möglich wird.

Innerhalb von CSS :has() können beliebige Selektoren verwendet werden, einschließlich Pseudo-Klassen und -Elemente. form:has(input:focus) selektiert ein Formular, das ein fokussiertes Input enthält. .menu:has(li:hover) selektiert ein Menü, in dem gerade über einem Listenpunkt gehovt wird. article:has(h2 + p) selektiert Artikel, in denen einem h2 direkt ein p folgt. Die Selektoren in CSS :has() sind relativ zum selektierten Element — sie beginnen den Matching-Prozess als würde man :scope voranstellen.


/* CSS :has() — practical patterns */

/* Parent selector: card with image gets different layout */
.card:has(img) {
  grid-template-rows: auto 1fr auto; /* separate image row */
}
.card:not(:has(img)) {
  padding-top: 2rem; /* extra top space without image */
}

/* Sibling context: label after a focused input */
.field:has(input:focus) label {
  color: #7c3aed;
  transform: translateY(-0.25rem);
  transition: all 0.2s ease;
}

/* State-based layout: form with invalid fields */
form:has(:invalid:not(:placeholder-shown)) .form-hint {
  display: block; /* show hint only when user has interacted */
}

/* Container query alternative: section with many items */
.grid-container:has(:nth-child(n+7)) {
  grid-template-columns: repeat(4, 1fr); /* switch to 4 cols at 7+ items */
}
.grid-container:not(:has(:nth-child(n+7))) {
  grid-template-columns: repeat(3, 1fr);
}

/* Interactive navigation: highlight parent of active item */
nav:has(.nav-item.active) {
  border-bottom: 2px solid #7c3aed;
}

3. Der Parent-Selektor: Eltern auf Basis von Kindern stylen

Der meistgenannte Anwendungsfall von CSS :has() ist der Parent-Selektor: Ein Elternelement wird auf Basis seiner Kindelemente gestylt. Das einfachste Beispiel: Ein Listenitem, das einen ausgewählten Zustand haben soll, wenn sein verstecktes Checkbox-Kind aktiviert ist. li:has(input[type=checkbox]:checked) selektiert genau diese Items — ohne JavaScript, ohne Class-Toggling, ohne Event-Handler. Das Checkbox-Element kann visuell versteckt sein, der Zustand ist trotzdem für CSS :has() sichtbar.

Ein fortgeschritteneres Beispiel: Eine Akkordeon-Komponente, bei der der Titel unterschiedlich gestylt ist, je nachdem ob der Inhalt sichtbar oder verborgen ist. Mit einem details-Element und CSS :has() lässt sich das ohne JavaScript realisieren: details:has(> summary + *) prüft, ob ein details-Element mehr als nur eine summary enthält — das ist immer der Fall, aber kombiniert mit details[open]:has(> summary) kann man die offene und geschlossene Version unterschiedlich stylen. CSS :has() macht die HTML-Semantik für das Styling nutzbar.

Noch direkter: label:has(+ input:required) selektiert ein Label, das von einem Pflichtfeld gefolgt wird. Das ermöglicht, Pflichtfeld-Labels automatisch mit einem Sternchen via CSS ::after zu markieren — rein deklarativ, ohne HTML-Änderungen, ohne JavaScript. Das ist der philosophische Kern von CSS :has(): CSS kann jetzt Fragen über den Zustand des umgebenden HTML-Kontexts beantworten und direkt darauf reagieren.

4. Conditional Styling: Layouts auf Basis von Kindelementen

CSS :has() ermöglicht echtes Conditional Styling auf CSS-Ebene — Layout-Entscheidungen, die auf dem tatsächlichen Inhalt basieren statt auf manuell gesetzten Klassen. Karten-Komponenten sind ein ideales Beispiel: Eine Karte mit Bild braucht ein anderes Layout als eine Karte ohne Bild. Mit CSS :has() lässt sich das direkt ausdrücken, ohne dass der Server oder JavaScript beim Rendern eine Klasse setzen müsste.

Ähnlich verhält es sich mit Inhaltsblöcken in CMS-Systemen: Ein article-Element, das eine figure enthält, bekommt ein Float-Layout. Eines ohne figure bleibt in einem linearen Textfluss. Mit CSS :has() lässt sich dieser Conditional-Styling-Ansatz direkt im Stylesheet ausdrücken: article:has(figure) { display: grid; grid-template-columns: 1fr 1fr; } und article:not(:has(figure)) { max-width: 65ch; }. Das CMS gibt einfach semantisches HTML aus, das CSS übernimmt alle Layout-Entscheidungen.

Besonders mächtig wird CSS :has() als Alternative zu Container Queries für inhaltssensitive Layouts: .card-grid:has(:nth-child(n+5)) kann das Grid-Layout ändern, sobald mehr als vier Karten vorhanden sind. Das ist kein echter Container-Query-Ersatz, aber für bestimmte diskrete Zustandsänderungen — wenige vs. viele Items — ist CSS :has() direkter und benötigt kein zusätzliches CSS-Feature.


/* Conditional Styling with CSS :has() — no JavaScript needed */

/* Card layout adapts based on content presence */
.card { display: flex; flex-direction: column; gap: 1rem; padding: 1.5rem; }

.card:has(.card__image) {
  padding: 0; /* image-first: no top padding, image touches border */
  overflow: hidden;
}

.card:has(.card__image) .card__body {
  padding: 1.25rem 1.5rem 1.5rem;
}

/* Article: grid layout only when figure is present */
.article-body:has(figure) {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  align-items: start;
}

.article-body:has(figure) figure {
  grid-column: 2;
  grid-row: 1 / span 3;
  position: sticky;
  top: 1rem;
}

/* Highlight required labels automatically via :has() */
.field:has(input[required]) > label::after,
.field:has(select[required]) > label::after {
  content: " *";
  color: #dc2626;
  font-weight: 700;
}

/* Section with overflow: show scroll indicator */
.content-box:has(.inner:overflow) {
  position: relative;
}
.content-box:has(> *:nth-child(n+4)) .show-more {
  display: block; /* reveal "show more" link if 4+ children exist */
}

5. Formular-States: Validierung ohne JavaScript

Formulare sind der Anwendungsbereich, bei dem CSS :has() den größten unmittelbaren Produktivitätsvorteil bringt. Die Kombination von CSS :has() mit Formular-Pseudo-Klassen wie :invalid, :valid, :required, :optional, :focus, :placeholder-shown und :checked ermöglicht deklarative Formular-UX, die früher ausschließlich JavaScript-Domäne war. Ein Submit-Button, der deaktiviert aussieht, solange das Formular ungültige Pflichtfelder enthält: form:has(:invalid) [type=submit].

Das Muster :invalid:not(:placeholder-shown) ist eine wichtige Präzisierung: :invalid allein ist bei Pflichtfeldern sofort beim Laden des Formulars aktiv, noch bevor der Nutzer etwas eingegeben hat. :placeholder-shown ist aktiv, wenn der Placeholder sichtbar ist — also wenn das Feld noch leer ist. Die Kombination :invalid:not(:placeholder-shown) zeigt Fehler nur, wenn der Nutzer tatsächlich etwas eingegeben hat, das ungültig ist. Das ist mit CSS :has() auf dem Container ausdrückbar: form:has(:invalid:not(:placeholder-shown)) selektiert das Formular in genau dem Zustand, in dem Fehlerfeedback angemessen ist.

6. :has() mit anderen Pseudo-Klassen kombinieren

CSS :has() entfaltet seine volle Stärke in Kombination mit anderen Pseudo-Klassen und Pseudo-Elementen. :is() und :where() innerhalb von CSS :has() erlauben komplexe Selektionslogiken mit mehreren Bedingungen. section:has(:is(h2, h3)) selektiert Sektionen, die entweder einen h2 oder einen h3 enthalten. :not(:has(img)) invertiert die Bedingung und selektiert Elemente, die kein Bild enthalten. Diese logischen Kombinationen machen CSS :has() zu einem vollständigen Bedingungssystem im Stylesheet.

Die Kombination von CSS :has() mit :hover, :focus-within und :active eröffnet neue interaktive Styling-Muster. .card:has(.card__cta:hover) hebt die gesamte Karte hervor, wenn über den CTA-Button gehovt wird — ohne JavaScript, ohne komplexe CSS-Selektoren, die rückwärts durch den DOM navigieren müssten. nav:has(a:focus-visible) kann das gesamte Navigations-Element hervorheben, wenn ein Tastaturnutzer in der Navigation navigiert.


/* CSS :has() with form validation pseudo-classes */

/* Submit button: visually disabled when form has invalid fields */
form:has(:invalid:not(:placeholder-shown)) [type="submit"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Field error: show error message only after user interaction */
.field:has(input:invalid:not(:placeholder-shown)) .field__error {
  display: block;
  color: #dc2626;
  font-size: 0.75rem;
  margin-top: 0.25rem;
}

/* Field success: green border when valid and filled */
.field:has(input:valid:not(:placeholder-shown)) input {
  border-color: #16a34a;
  outline-color: #16a34a;
}

/* Show password requirements only when password field is focused */
form:has(input[type="password"]:focus) .password-requirements {
  display: block;
  animation: fadeIn 0.2s ease;
}

/* Highlight entire card on CTA hover — no JS needed */
.card:has(.card__cta:hover) {
  box-shadow: 0 8px 30px rgba(124, 58, 237, 0.2);
  transform: translateY(-2px);
  transition: all 0.2s ease;
}

/* Auto-mark required labels */
.form-field:has(input[required]) label::after {
  content: " *";
  color: #dc2626;
}

CSS :has() verändert, wie man interaktive Navigationskomponenten baut. Ein Dropdown-Menü, das seinen Parent-Link hervorhebt, wenn eines der Untermenü-Items aktiv ist: .nav-item:has(.submenu-item.active). Ein Mega-Menü, das den Hauptnavigationsbereich hervorhebt, wenn irgendwo innerhalb gehovt wird: .main-nav:has(a:hover). Diese Selektoren waren vorher nur über JavaScript erreichbar, das das DOM aufwärts traversiert und Klassen setzt.

Besonders nützlich ist CSS :has() für State-abhängige Layouts in Single-Page-Applications. Wenn ein Modal geöffnet ist, soll der Body-Scroll gesperrt werden: Früher setzte JavaScript overflow: hidden auf dem Body. Mit CSS :has() kann man das rein im CSS ausdrücken: body:has(dialog[open]) selektiert den Body, wenn ein offenes Dialog-Element vorhanden ist. body:has(.drawer.open) sperrt den Scroll, wenn eine Sidebar geöffnet ist. Das State-Management bleibt im HTML, das visuelle Feedback im CSS.

8. Browser-Support-Stand 2026

CSS :has() hat 2026 eine sehr gute Browser-Unterstützung erreicht. Chrome und Edge unterstützen CSS :has() seit Version 105 (August 2022), Safari seit Version 15.4 (März 2022), Firefox seit Version 121 (Dezember 2023). Die globale Nutzungsabdeckung liegt 2026 über 94%. Internet Explorer wird nicht unterstützt — das war zu erwarten. Der einzige relevante Browser, der CSS :has() spät implementiert hat, war Firefox, aber seit Version 121 ist auch dieser Einwand obsolet.

Für Progressive Enhancement empfiehlt sich @supports selector(:has(a)). Damit lässt sich eine Basis-Version des Layouts für ältere Browser definieren und die CSS :has()-verbesserte Version für moderne Browser hinzufügen. In den meisten Projekten ist diese Vorsichtsmaßnahme 2026 optional geworden — die Browser-Abdeckung ist ausreichend für Produktionseinsatz ohne Fallback. Anders als bei einigen anderen modernen CSS-Features gibt es bei CSS :has() keine Browserbugs mehr in den aktuellen Versionen, die den Produktionseinsatz einschränken würden.

9. :has() vs. JavaScript-Lösungen im Vergleich

Der direkte Vergleich zwischen CSS :has() und JavaScript-basierten Lösungen offenbart erhebliche Unterschiede in Komplexität, Performance und Wartbarkeit. Für alle Anwendungsfälle, die CSS :has() abdeckt, ist die reine CSS-Lösung vorzuziehen — sie ist synchron mit dem Browser-Rendering-Prozess, benötigt keinen Event-Listener-Lifecycle, und ist in einer einzigen CSS-Regel ausdrückbar.

Anwendungsfall JavaScript-Lösung CSS :has() Lösung Vorteil
Parent hervorheben bei Kinder-Hover mouseover + classList.add auf Parent .parent:has(.child:hover) Keine Event-Listener, keine Latenz
Formular-State input-Event + Validierung + Class-Toggle form:has(:invalid) Synchron, kein JS-Bundle
Layout nach Inhalt DOM-Prüfung + conditional classList .card:has(img) Kein Render-Blocking, SSR-kompatibel
Body-Scroll sperren document.body.style.overflow = 'hidden' body:has(dialog[open]) State im HTML, kein JS-Zustandsmanagement
Aktiven Elternavig.-Punkt markieren DOM-Traversal + parentElement.classList .nav-item:has(.active) Lesbar, wartbar, zero JS

Die CSS :has()-Lösungen sind in allen Fällen kürzer, lesbarer und wartbarer. Der einzige Bereich, in dem JavaScript weiterhin benötigt wird, sind echte Daten- und Logikoperationen, die über CSS-Selektoren hinausgehen: Netzwerkanfragen, komplexe State-Management-Logik, dynamisch gerechnete Werte. Reine Styling-Reaktionen auf DOM-Zustände sind mit CSS :has() vollständig im Stylesheet abbildbar.

Mironsoft

Modernes CSS, interaktive Komponenten und JS-freie UI-Lösungen

CSS :has() für Ihre Komponenten einsetzen?

Wir überführen JavaScript-basierte Styling-Logik in deklaratives CSS mit :has(), :is(), Container Queries und modernen Pseudo-Klassen — weniger JS-Bundle, schnellere Ladezeiten, wartbarere Codebasis.

CSS-Audit

Identifikation von JS-Styling-Logik, die mit :has() ins CSS überführt werden kann

Formular-UX

Validierung, States und konditionelles Feedback mit modernen CSS-Features

Komponenten

Content-adaptive Karten, Navigation und interaktive UI-Elemente ohne JS

10. Zusammenfassung

CSS :has() ist das mächtigste neue CSS-Feature der letzten Jahre. Als relationaler Pseudo-Klassen-Selektor erlaubt CSS :has() es, Elemente auf Basis ihrer Nachfolger und Geschwister zu stylen — was vorher nur mit JavaScript möglich war. Parent-Selektoren, Conditional Styling auf Basis von Inhalt, Formular-State-Visualisierung ohne Event-Handler, Body-Scroll-Kontrolle über Dialog-Zustände: Alles rein deklarativ im Stylesheet ausdrückbar. Der Browser-Support ist 2026 produktionsreif mit über 94% globaler Abdeckung.

Der strategische Wert von CSS :has() liegt im Rückbau von JavaScript-Abhängigkeiten für reine Styling-Reaktionen auf DOM-Zustände. Jede Styling-Logik, die aus dem JavaScript-Bundle ins CSS verschoben wird, verbessert die Initialisierungsperformance, reduziert die Komplexität des JavaScript-Codes und macht das Styling-Verhalten für Designer und Frontend-Entwickler im Stylesheet sichtbar. CSS :has() sollte 2026 in jedem modernen Projekt als erste Wahl für DOM-Zustands-reaktives Styling evaluiert werden.

CSS :has() — Das Wichtigste auf einen Blick

Parent-Selektor

.parent:has(.child) — selektiert das Elternelement, wenn das Kind existiert oder einen bestimmten Zustand hat.

Formular-States

form:has(:invalid:not(:placeholder-shown)) — Fehler-Feedback nur nach Nutzerinteraktion, kein JavaScript benötigt.

Browser-Support 2026

Chrome 105+, Safari 15.4+, Firefox 121+. Über 94% globale Abdeckung. Produktionsreif ohne Fallback.

Progressive Enhancement

@supports selector(:has(a)) für Fallback auf ältere Browser. In den meisten Projekten 2026 optional.

11. FAQ: CSS :has()

1Was macht CSS :has()?
Relationaler Pseudo-Klassen-Selektor — selektiert ein Element, wenn es bestimmte Nachfolger oder Geschwister enthält. Der lange fehlende Parent-Selektor in CSS.
2Welche Browser unterstützen :has()?
Chrome/Edge 105+, Safari 15.4+, Firefox 121+. Über 94% globale Abdeckung 2026. Produktionsreif.
3Kann :has() JS für Formular-States ersetzen?
Für visuelles Formular-Styling: ja. form:has(:invalid) ersetzt klassisches JS für State-Feedback ohne Event-Handler.
4:has() mit :not() kombinieren?
.card:not(:has(img)) für Karten ohne Bild, .card:has(img) für Karten mit Bild — zwei Layouts, keine Klassen, kein JS.
5@supports für :has()?
@supports selector(:has(a)) — Progressive Enhancement für ältere Browser. In den meisten Projekten 2026 optional.
6body:has(dialog[open]) möglich?
Ja. body:has(dialog[open]) { overflow: hidden } — Body-Scroll sperren ohne JavaScript. Klassische :has()-Anwendung.
7Was ist :invalid:not(:placeholder-shown)?
Zeigt Validierungsfehler nur nach Nutzerinteraktion. :placeholder-shown signalisiert ein leeres Feld — die Kombination verhindert sofortige Fehleranzeige beim Laden.
8:has() als Container-Query-Ersatz?
Eingeschränkt: :has(:nth-child(n+5)) kann Layout bei >4 Kindern ändern. Für messbasierte Queries bleibt @container die richtige Wahl.
9Performance-Bedenken?
Für typische Anwendungsfälle keine Bedenken. Sehr komplexe Selektoren mit tiefen Traversals können Style-Recalculation verlangsamen — pragmatisch halten.
10Geschwister-Kontext mit :has()?
.element:has(+ .sibling) selektiert ein Element, das von einem bestimmten Geschwister gefolgt wird. Neuer Selektions-Kontext, der vorher in CSS nicht möglich war.