mitmario.dev

Der :has() Selektor

: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.

Steckbrief :has()
Syntax
:has(<selektor-liste>)
Initialwert
N/A (Pseudo-Klasse)
Vererbt
Nein
Animierbar
Nein
Kategorie
Selektoren
Werte
6
Beispiele
6
Browser
5 von 5
E-Mail
1 von 5
Stand
03.07.2026

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

6 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

6 Beispiele

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

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

5 Punkte
  • :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

5 von 5

:has() wird von allen modernen Browsern unterstützt. Firefox hat als letzter Browser den Support ergänzt (Version 121).

  • Chrome

    Komplett

  • Firefox

    Komplett

    Seit Version 121

  • Safari

    Komplett

    Seit Version 15.4

  • Edge

    Komplett

  • Opera

    Komplett

Email-Kompatibilität

1 von 5

: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.

  • Apple Mail

    Komplett

    Neuere Versionen

  • Gmail

    Keine

    Kein Support

  • Outlook

    Keine

    Kein Support

  • Yahoo Mail

    Keine

    Kein Support

  • Thunderbird

    Teilweise

    Nur neuere Versionen

Newsletter

einmal die Woche, kostenlos

Neue Artikel und Videos, was sich auf der Lernplattform tut, und ab und zu ein Sonderpreis zum Kursstart. Kurz genug, dass du es auch liest.

Zuletzt verschickt · KW 40

Einmal alles neu, und die Lernplattform ist da

Danach kommt eine Mail mit Bestätigungslink, erst dann bist du eingetragen. Abmelden geht unten in jeder Ausgabe. Mehr dazu in der Datenschutzerklärung.