TL;DR: 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.
Mehr Artikel entdecken
htmx: Interactive Websites Without JavaScript Frameworks 🔄
TL;DR: htmx makes your websites interactive without React, Angular, or Vue. A few HTML attributes replace tons of JavaScript while still delivering dynamic UIs. Here's how it works and when it makes sense. 🤔 What Is htmx, Anyway? Imagine making your website interactive without writing a single line
CSS :has() – The Parent Selector Everyone’s Been Waiting For 🎯
TL;DR: CSS :has() is the long-awaited parent selector – you can finally style parent elements based on their children. No more JavaScript hacks, no workarounds, just pure CSS. And yes, it works in all modern browsers. 🤔 Why CSS Never Had a Parent Selector If you've been writing CSS
Container Queries in CSS: Das Ende von Media Queries? 📦
Container Queries machen Schluss mit dem Viewport-Chaos! 📦 Wie du deine Komponenten endlich wirklich responsive machst – und warum Media Queries trotzdem nicht tot sind.
Angular und TailwindCSS
Angular und TailwindCSS sind ein starkes Duo. So richtest du Tailwind in deinem Angular-Projekt ein und nutzt Utility-First CSS effektiv.
Tailwind CSS vs. Vanilla CSS – Wann lohnt sich was? ⚖️
Tailwind CSS oder doch lieber Vanilla CSS? ⚖️ Ich zeige dir, wann sich welcher Ansatz wirklich lohnt – mit Praxisbeispielen und ehrlichen Vor- und Nachteilen!
CSS-Variablen: Flexibles Styling für deine Komponenten 🎨
CSS-Variablen machen dein Styling flexibler 🎯 Wie du sie nutzt, scopst und in Komponenten überschreibst, erkläre ich dir in diesem Guide! 🌈

