Der :has() Selektor
Zuletzt aktualisiert am 3. Juli 2026
:has() ist die Pseudo-Klasse, auf die CSS-Entwickler jahrelang gewartet haben: Sie wählt ein Element danach aus, was es enthält oder was ihm folgt. Eine Karte, die ein Bild enthält, ein Formularfeld mit ungültiger Eingabe, eine Überschrift, auf die direkt ein Absatz folgt: Alles ohne JavaScript und ohne zusätzliche Klassen. Weil :has() erstmals ein Element anhand seiner Kinder ansprechen kann, wird es oft Eltern-Selektor genannt.
:has(<selektor-liste>) N/A (Pseudo-Klasse) Grundlagen
Normale CSS-Selektoren lesen sich von links nach rechts immer tiefer in den DOM-Baum hinein: .card img wählt Bilder innerhalb einer Karte. Der umgekehrte Weg war lange unmöglich. Du konntest kein Element danach auswählen, was in ihm steckt. Genau diese Lücke schließt :has().
Der Selektor vor :has() ist der Anker: Das ist das Element, das am Ende gestylt wird. In den Klammern steht eine relative Selektor-Liste, die vom Anker aus geprüft wird. .card:has(img) bedeutet also: Wähle jede .card, die irgendwo ein <img> enthält. Gestylt wird die Karte, nicht das Bild.
In den Klammern funktionieren auch die vier Kombinatoren, die du aus Selektoren Erweitert kennst. Ohne Kombinator prüft :has() alle Nachfahren, mit > nur direkte Kinder, mit + und ~ sogar nachfolgende Geschwister. Damit ist :has() mehr als ein Eltern-Selektor: h2:has(+ p) wählt eine Überschrift aus, auf die direkt ein Absatz folgt. Das ging vorher mit keinem Selektor.
Richtig mächtig wird :has() in Kombination mit Zustands-Pseudo-Klassen wie :checked, :invalid oder :focus. Der Zustand eines Kind-Elements steuert dann das Styling des gesamten Containers, komplett ohne JavaScript.
Syntax & Werte
| Wert | Beschreibung | Beispiel |
|---|---|---|
:has(selektor) | Trifft Elemente, die mindestens einen passenden Nachfahren enthalten, egal wie tief verschachtelt. | article:has(img) |
:has(> selektor) | Trifft Elemente, die ein passendes direktes Kind haben. | ul:has(> li.aktiv) |
:has(+ selektor) | Trifft Elemente, auf die direkt ein passendes Geschwister-Element folgt. | h2:has(+ p) |
:has(~ selektor) | Trifft Elemente, denen irgendwo auf gleicher Ebene ein passendes Geschwister folgt. | input:has(~ .fehler) |
:has(sel1, sel2) | Trifft, wenn mindestens einer der Selektoren passt (ODER-Verknüpfung). | section:has(h2, h3) |
:not(:has(selektor)) | Kehrt die Auswahl um: Trifft Elemente, die keinen passenden Nachfahren enthalten. | .card:not(:has(img)) |
Beispiele
Karte mit Bild anders stylen
Der Klassiker: Eine Karte mit Bild bekommt ein anderes Styling als eine Karte ohne Bild. Mit :not(:has(img)) drehst du die Bedingung um und stylst gezielt die Karten ohne Bild.
/* Karte, die ein Bild enthält */
.card:has(img) {
border-color: #1a56db;
}
/* Karte ohne Bild */
.card:not(:has(img)) {
border-style: dashed;
background: #f8fafc;
}<div class="has-demo">
<div class="has-card">
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='160'%3E%3Crect width='400' height='160' fill='%231a56db'/%3E%3C/svg%3E" alt="">
<div class="has-card-body">
<h4>Karte mit Bild</h4>
<p>Blauer Rahmen dank :has(img).</p>
</div>
</div>
<div class="has-card">
<div class="has-card-body">
<h4>Karte ohne Bild</h4>
<p>Gestrichelt dank :not(:has(img)).</p>
</div>
</div>
</div>Formular: Label bei ungültiger Eingabe einfärben
Das Formularfeld reagiert auf den Validierungszustand seines Inputs: Label und Rahmen färben sich rot bei ungültiger und grün bei gültiger Eingabe. Ändere die Eingaben in der Vorschau und beobachte, wie das Styling live umschaltet.
/* Feld mit ungültiger Eingabe */
.form-field:has(input:invalid) label {
color: #dc2626;
}
.form-field:has(input:invalid) input {
border-color: #dc2626;
}
/* Feld mit gültiger Eingabe */
.form-field:has(input:valid) label {
color: #059669;
}<div class="form-demo">
<div class="form-field">
<label for="mail-ok">E-Mail (gültig)</label>
<input id="mail-ok" type="email" required value="mario@example.com">
</div>
<div class="form-field">
<label for="mail-err">E-Mail (ungültig)</label>
<input id="mail-err" type="email" required value="keine-email">
</div>
<p class="form-hint">Tipp: Ändere die Eingaben, das Styling schaltet live um.</p>
</div>Zustände abfragen: Checkbox schaltet die Karte um
Mit :has(input:checked) reagiert die komplette Karte auf den Zustand der Checkbox. Vor :has() brauchte man dafür JavaScript oder fragile Tricks mit dem Geschwister-Selektor.
.card:has(input:checked) {
background: #1e293b;
border-color: #1e293b;
}
.card:has(input:checked) label {
color: white;
}<div class="toggle-demo">
<div class="toggle-card">
<label><input type="checkbox"> Dark Mode aktivieren</label>
<p>Die ganze Karte reagiert auf die Checkbox, ohne eine Zeile JavaScript.</p>
</div>
</div>Geschwister prüfen mit Kombinatoren
Mit + in den Klammern prüft :has() das nachfolgende Geschwister-Element. h3:has(+ p) trifft nur Überschriften, auf die direkt ein Absatz folgt: Die erste Überschrift wird blau und rückt näher an ihre Unterzeile, die zweite bleibt unverändert.
/* Nur Überschriften, auf die direkt
ein Absatz folgt */
h3:has(+ p) {
color: #1a56db;
margin-bottom: 2px;
}<div class="sib-demo">
<h3>Überschrift mit Unterzeile</h3>
<p>Diese Unterzeile folgt direkt auf die Überschrift.</p>
<h3>Überschrift ohne Unterzeile</h3>
</div>Spezifität
:has() selbst fügt der Spezifität nichts hinzu. Wie bei :is() und :not() zählt stattdessen das spezifischste Argument in den Klammern voll mit. Ein #id-Selektor im Argument macht die ganze Regel also so stark wie eine ID, auch wenn das Element mit der ID gar nicht gestylt wird.
/* Spezifität (0,1,1): eine Klasse + ein Element */
.card:has(img) {
border-color: #1a56db;
}
/* Spezifität (1,1,0): das ID-Argument zählt voll mit */
.card:has(#logo) {
border-color: #dc2626;
}Behalte das im Hinterkopf, wenn eine :has()-Regel überraschend andere Regeln überschreibt: Der Übeltäter ist oft ein zu spezifisches Argument in den Klammern.
Performance
:has() ist aufwendiger als normale Selektoren, weil der Browser für jedes Anker-Element den Teilbaum in den Klammern prüfen muss, und zwar bei jeder DOM-Änderung neu. Moderne Browser optimieren das gut, aber du kannst mithelfen: Vermeide breite Anker wie body:has(...), :root:has(...) oder *:has(...) und schränke die Suche mit dem Kind-Kombinator ein. .card:has(> img) ist deutlich günstiger als body:has(img), weil der Browser nur eine Ebene statt des gesamten Dokuments prüfen muss.
Barrierefreiheit
Wenn du mit :has(:invalid) Formular-Feedback gibst, verlasse dich nicht auf Farbe allein. Rot und Grün sind für Menschen mit Farbfehlsichtigkeit schwer zu unterscheiden. Kombiniere die Färbung mit einem Textausweis oder Symbol, zum Beispiel einer Fehlermeldung unter dem Feld.
Zustandsabhängiges Styling per :has(:checked) oder :has(:focus) ist rein visuell. Screenreader bekommen davon nichts mit. Sorge dafür, dass der Zustand auch semantisch stimmt: Eine echte Checkbox mit Label transportiert ihren Zustand von selbst, ein rein optisch umgefärbtes <div> nicht.
Häufige Fehler
:has()in:has()verschachteln..a:has(.b:has(.c))ist ungültig, die Verschachtelung ist per Spezifikation verboten. Schreibe stattdessen.a:has(.b .c)oder zwei getrennte Regeln.- Pseudo-Elemente im Argument verwenden.
.card:has(::before)funktioniert nicht, Pseudo-Elemente sind in den Klammern von:has()nicht erlaubt. - Das falsche Element stylen wollen.
.card:has(img)stylt die Karte, nicht das Bild. Willst du das Bild stylen, brauchst du:has()gar nicht:.card imgreicht. - Zu breite Anker verwenden.
body:has(.sidebar)zwingt den Browser, bei jeder DOM-Änderung das komplette Dokument zu prüfen. Nutze möglichst spezifische Anker und den Kind-Kombinator. - Vergessen, dass die Selektor-Liste nicht fehlertolerant ist. Anders als bei
:is()und:where()macht ein einziger ungültiger Selektor im Argument die komplette Regel ungültig.
Browser-Kompatibilität
:has() wird von allen modernen Browsern unterstützt. Firefox hat als letzter Browser den Support ergänzt (Version 121).
| Client | Unterstützung | Hinweise |
|---|---|---|
| Chrome | Komplett | — |
| Firefox | Komplett | Seit Version 121 |
| Safari | Komplett | Seit Version 15.4 |
| Edge | Komplett | — |
| Opera | Komplett | — |
Email-Kompatibilität
:has() ist für E-Mails ungeeignet. Die großen Webmail-Clients wie Gmail, Outlook und Yahoo unterstützen es nicht, nur Apple Mail und neuere Thunderbird-Versionen stellen es dar.
| Client | Unterstützung | Hinweise |
|---|---|---|
| Apple Mail | Komplett | Neuere Versionen |
| Gmail | Keine | Kein Support |
| Outlook | Keine | Kein Support |
| Yahoo Mail | Keine | Kein Support |
| Thunderbird | Teilweise | Nur neuere Versionen |
Verwandte Eigenschaften
Bleib auf dem Laufenden
Erhalte neue Inhalte direkt in dein Postfach
Was erwartet dich?
Neue Artikel & Videos
Wenn ich was Neues veröffentliche, erfährst du's hier zuerst.
Plattform & Kurs-Updates
Neue Kurse, Referenz-Updates und gelegentlich Sonderpreise zum Launch.
Einmal die Woche in deinem Postfach
Kurz, auf den Punkt, kein Spam.