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.

Syntax
:has(<selektor-liste>)
Initialwert
N/A (Pseudo-Klasse)
Vererbt
Nein
Animierbar
Nein

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 mit und 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;
}

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.

Formularfeld reagiert auf :invalid
/* 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;
}

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.

Karte reagiert auf :checked
.card:has(input:checked) {
  background: #1e293b;
  border-color: #1e293b;
}

.card:has(input:checked) label {
  color: white;
}

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.

Überschrift mit nachfolgendem Absatz
/* Nur Überschriften, auf die direkt
   ein Absatz folgt */
h3:has(+ p) {
  color: #1a56db;
  margin-bottom: 2px;
}

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 img reicht.
  • 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).

ClientUnterstützungHinweise
ChromeKomplett
FirefoxKomplettSeit Version 121
SafariKomplettSeit Version 15.4
EdgeKomplett
OperaKomplett

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.

ClientUnterstützungHinweise
Apple MailKomplettNeuere Versionen
GmailKeineKein Support
OutlookKeineKein Support
Yahoo MailKeineKein Support
ThunderbirdTeilweiseNur neuere Versionen

Verwandte Eigenschaften

Bleib auf dem Laufenden

Erhalte neue Inhalte direkt in dein Postfach

Mit der Anmeldung akzeptierst du unsere Datenschutzerklärung. Du kannst dich jederzeit über den Link in den E-Mails wieder abmelden.

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.