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
CSS Scroll-Driven Animations – Animationen ohne JavaScript 🎬
TL;DR: CSS Scroll-Driven Animations ermöglichen es dir, Elemente basierend auf der Scroll-Position zu animieren – ganz ohne JavaScript. Mit animation-timeline: scroll() und animation-timeline: view() kannst du Fortschrittsbalken, Fade-Ins und Parallax-Effekte rein deklarativ umsetzen. Performant, elegant und zukunftssicher. 🤔 Was sind Scroll-Driven Animations? Stell dir vor, du scrollst durch eine Website und
CSS Grid: Das fehlende Gegenstück zu Flexbox 🧩
TL;DR: CSS Grid ist das fehlende Puzzlestück zu Flexbox. Während Flexbox eindimensionale Layouts meistert, gibt dir Grid die volle Kontrolle über Zeilen UND Spalten gleichzeitig. In diesem Artikel lernst du alles von den Basics bis Subgrid – mit praktischen Beispielen, die du sofort einsetzen kannst. Du kennst Flexbox? Gut. Dann
View Transitions API: Smooth Page Transitions ohne Framework ✨
TL;DR: Die View Transitions API bringt butterweiche Seitenuebergaenge direkt in den Browser - ganz ohne Framework, ganz ohne JavaScript-Bibliothek. Einfach CSS und ein paar Zeilen JS. Hier erfaehrst du, wie das funktioniert und warum du es sofort ausprobieren solltest. 🤔 Was sind View Transitions ueberhaupt? Kennst du das? Du klickst
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.

