CSS :has() ist der lang ersehnte Parent Selector, du kannst jetzt endlich Eltern-Elemente basierend auf ihren Kindern stylen. Kein JavaScript mehr nötig, kein Workaround, einfach pures CSS. Und ja, es funktioniert in allen modernen Browsern.đ€ Warum gab es nie einen Parent Selector?
Wenn du schon lĂ€nger CSS schreibst, kennst du das Problem. Du willst ein Element stylen, aber die Bedingung hĂ€ngt von einem Kind-Element ab. Ein Formular rot umranden, wenn ein Input ungĂŒltig ist? Ein Card-Layout anpassen, wenn ein Bild vorhanden ist? Klassisches Szenario, und klassisch unmöglich mit CSS.
Der Grund war simpel: Performance. Browser rendern CSS von rechts nach links. Ein Parent Selector hĂ€tte bedeutet, dass der Browser bei jedem Element den gesamten DOM-Baum nach oben traversieren mĂŒsste. Das war lange ein No-Go.
Aber die Browser-Engines sind besser geworden. Viel besser. Und so wurde :has() geboren.
đ Was ist :has() und wie funktioniert es?
Mit :has() kannst du ein Element auswÀhlen, das ein bestimmtes Kind-Element enthÀlt. Du schreibst quasi: "WÀhle mir das Element, das dieses Kind hat."
/* WÀhle jeden div, der ein p-Element enthÀlt */
div:has(p) {
border: 2px solid blue;
}
/* WÀhle jeden article, der ein img enthÀlt */
article:has(img) {
grid-template-columns: 1fr 1fr;
}Das Geniale: :has() ist nicht nur ein Parent Selector. Es ist ein relativer Selektor. Du kannst jede beliebige Selektor-Logik darin verwenden.
/* WÀhle ein label, dessen nÀchstes Geschwister-Element ein :checked Input ist */
label:has(+ input:checked) {
font-weight: bold;
color: green;
}đ» Praktische Beispiele
Formular-Validierung stylen
Statt JavaScript zu nutzen, um Klassen zu togglen, kannst du jetzt direkt mit CSS auf den Zustand deiner Inputs reagieren.
/* Fieldset rot umranden, wenn ein ungĂŒltiger Input drin ist */
fieldset:has(input:invalid) {
border-color: red;
background: #fff0f0;
}
/* Fieldset grĂŒn, wenn alle Inputs gĂŒltig sind */
fieldset:has(input:valid):not(:has(input:invalid)) {
border-color: green;
background: #f0fff0;
}
/* Submit-Button visuell deaktivieren */
form:has(input:invalid) button[type="submit"] {
opacity: 0.5;
pointer-events: none;
}Card Layouts dynamisch anpassen
Stell dir ein Card-Component vor. Manchmal hat es ein Bild, manchmal nicht. Mit :has() passt du das Layout automatisch an.
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
.card:not(:has(img)) {
padding: 2rem;
}
/* Card mit Video bekommt mehr Platz */
.card:has(video) {
grid-column: span 2;
}Navigation Highlights
/* Nav-Item hervorheben, wenn es den aktiven Link enthÀlt */
nav li:has(a.active) {
background: #e0e7ff;
border-radius: 8px;
}
/* Dropdown-Pfeil drehen, wenn UntermenĂŒ offen ist */
nav li:has(.submenu:not([hidden])) > .arrow {
transform: rotate(180deg);
}
âïž Kombinieren mit anderen Selektoren
Die wahre Power von :has() entfaltet sich in Kombination mit :not(), :is() und :where().
/* Element, das KEIN Bild enthÀlt */
.card:not(:has(img)) {
min-height: 200px;
}
/* Element, das entweder ein img ODER ein video enthÀlt */
.card:has(:is(img, video)) {
aspect-ratio: 16 / 9;
}
/* SpezifitÀt-freie Variante mit :where() */
.card:has(:where(img, video)) {
overflow: hidden;
}Du kannst :has() auch verschachteln, ja, wirklich:
/* Section, die eine Card enthÀlt, die ein Bild enthÀlt */
section:has(.card:has(img)) {
padding: 2rem;
}đš Real-World Use Cases
Hat eine aktivierte Checkbox
/* Zeile hervorheben, wenn Checkbox gecheckt */
tr:has(input[type="checkbox"]:checked) {
background: #e0f2fe;
}
/* Dark Mode Toggle ohne JavaScript */
body:has(#dark-mode:checked) {
--bg: #1a1a2e;
--text: #e0e0e0;
background: var(--bg);
color: var(--text);
}Hat einen leeren Input
/* Placeholder-Styling, wenn Input leer ist */
.form-group:has(input:placeholder-shown) label {
color: #999;
transform: translateY(0);
}
/* Floating Label Effekt */
.form-group:has(input:not(:placeholder-shown)) label {
transform: translateY(-1.5rem);
font-size: 0.75rem;
color: #3b82f6;
}Hat ein bestimmtes Kind-Element
/* Sidebar nur anzeigen, wenn Content vorhanden */
.layout:has(.sidebar:not(:empty)) {
grid-template-columns: 1fr 300px;
}
.layout:not(:has(.sidebar:not(:empty))) {
grid-template-columns: 1fr;
}
/* Figcaption-Abstand nur wenn vorhanden */
figure:has(figcaption) img {
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}â ïž Performance, Worauf du achten solltest
Ja, :has() ist mĂ€chtig. Aber mit groĂer Macht kommt groĂe Verantwortung.
Ein paar Regeln:
- Vermeide zu breite Selektoren.
:has(div)auf dembodyzwingt den Browser, den gesamten DOM zu durchsuchen. - Sei spezifisch.
.card:has(> img)(direktes Kind) ist performanter als.card:has(img)(beliebige Tiefe). - Nutze den direkten Kind-Kombinator
>wo möglich. - Teste mit groĂen DOMs. Was bei 50 Elementen flĂŒssig lĂ€uft, kann bei 5000 ruckeln.
/* â Zu breit â potenziell langsam */
div:has(span) { ... }
/* â
Spezifisch und performant */
.card:has(> .card-image) { ... }đĄïž Browser Support
:has() wird von allen modernen Browsern unterstĂŒtzt:
| Browser | Version | Seit |
|---|---|---|
| Chrome | 105+ | August 2022 |
| Firefox | 121+ | Dezember 2023 |
| Safari | 15.4+ | MĂ€rz 2022 |
| Edge | 105+ | August 2022 |
Safari war hier tatsĂ€chlich der Vorreiter, ungewöhnlich, aber willkommen. Firefox hat etwas lĂ€nger gebraucht, aber seit Ende 2023 sind alle groĂen Browser an Bord.
đĄ Fazit
CSS :has() ist kein Hype, es ist ein Game Changer. Jahrelang haben wir JavaScript-Workarounds gebaut, Klassen hin- und hertoggled und uns mit dem Kaskadenmodell herumgeschlagen. Das ist jetzt vorbei.
Du kannst Parent-Elemente stylen, auf ZustĂ€nde von Kind-Elementen reagieren und komplexe UI-Logik direkt in CSS abbilden. Die Browser-UnterstĂŒtzung ist da, die Performance ist gut (wenn du es richtig machst), und die Möglichkeiten sind endlos.
Also: Fang an, :has() zu nutzen. Dein CSS wird es dir danken.

