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.
- Syntax
- :has(<selektor-liste>)
- Initialwert
- N/A (Pseudo-Klasse)
- Vererbt
- Nein
- Animierbar
- Nein
- Kategorie
- Selektoren
- Werte
- 6
- Beispiele
- 6
- Browser
- 5 von 5
- 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 WerteBeispiele
6 BeispieleDer 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> 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> 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> 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>