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.
Inhaltsverzeichnis
- 1. Was CSS :has() wirklich ist
- 2. Syntax und Grundregeln
- 3. Der Parent-Selektor: Eltern auf Basis von Kindern stylen
- 4. Conditional Styling: Layouts auf Basis von Kindelementen
- 5. Formular-States: Validierung ohne JavaScript
- 6. :has() mit anderen Pseudo-Klassen kombinieren
- 7. Navigation und interaktive Komponenten
- 8. Browser-Support-Stand 2026
- 9. :has() vs. JavaScript-Lösungen im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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;
}
7. Navigation und interaktive Komponenten
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()?
2Welche Browser unterstützen :has()?
3Kann :has() JS für Formular-States ersetzen?
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?
body:has(dialog[open]) { overflow: hidden } — Body-Scroll sperren ohne JavaScript. Klassische :has()-Anwendung.7Was ist :invalid:not(:placeholder-shown)?
:placeholder-shown signalisiert ein leeres Feld — die Kombination verhindert sofortige Fehleranzeige beim Laden.8:has() als Container-Query-Ersatz?
:has(:nth-child(n+5)) kann Layout bei >4 Kindern ändern. Für messbasierte Queries bleibt @container die richtige Wahl.9Performance-Bedenken?
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.