CSS hat sich in den letzten Jahren deutlich weiterentwickelt. Mit Selektoren wie :is(), :where() oder :has() sind Werkzeuge hinzugekommen, die nicht nur Komfortfunktionen sind, sondern einen echten Einfluss darauf haben, wie wir Komponenten und UI-Logik denken.
Viele Teams nutzen diese Selektoren heute jedoch kaum, oft aus Gewohnheit oder Unsicherheit. Dabei lösen sie Probleme, die in großen Codebases regelmäßig auftreten, wie zum Beispiel komplexe Selektoren, fragile Komponenten, übermäßige Spezifität und unnötige Abhängigkeiten zu JavaScript.
Dieses Türchen zeigt, wie moderne Selektoren funktionieren, welche Probleme sie lösen und wie man sie sinnvoll in realen Projekten einsetzt.
:is() – Komplexe Selektoren vereinfachen
Der Selektor :is() erlaubt es, mehrere Varianten in einem einzigen Selektor zusammenzufassen. Das reduziert Wiederholungen, senkt die Fehleranfälligkeit und kann die Lesbarkeit verbessern.
Ein Beispiel aus einem Navigations-Menü:
nav :is(a, button, [role="menuitem"]) {
padding: 0.5rem 1rem;
display: inline-flex;
align-items: center;
}
Früher hätte man das so geschrieben:
nav a,
nav button,
nav [role="menuitem"] {
padding: 0.5rem 1rem;
display: inline-flex;
align-items: center;
}
Die Funktionalität ist identisch, aber :is() reduziert Wiederholungen und die Gefahr, einen Selektor zu vergessen.
Forgiving Selector List
Ein wichtiger Vorteil von :is() und :where(): Sie verwenden eine fehlertolerante Selektorliste. Bei traditionellen Selektorlisten wird die gesamte Regel verworfen, wenn ein Selektor ungültig ist:
/* Wird komplett ignoriert, weil :future-pseudo ungültig ist */
nav a,
nav :future-pseudo {
color: blue;
}
Mit :is() werden ungültige Selektoren einfach übersprungen:
/* Funktioniert für nav a, der ungültige Teil wird ignoriert */
nav :is(a, :future-pseudo) {
color: blue;
}
Das ist besonders wertvoll für Progressive Enhancement und zukunftssicheren Code.
Spezifität beachten
Wichtig:
:is() übernimmt die höchste Spezifität der enthaltenen Selektoren.
Beispiel:
:is(.btn, #cta) { ... }
Spezifität: 1-0-0 (wegen #cta)
In professionellen Codebases sollte man :is() deshalb bewusst einsetzen.
:where() – Selektoren mit Spezifität 0-0-0
:where() funktioniert ähnlich wie :is(), setzt aber die Spezifität seines Inhalts auf 0-0-0. Selektoren außerhalb von :where() behalten ihre normale Spezifität:
.container :where(.card .title) { ... }
/* Spezifität: 0-1-0 (nur .container zählt) */
Beispiel:
:where(.card .title) {
margin-bottom: 0.5rem;
}
Egal wie komplex der Selektor: Spezifität bleibt 0-0-0.
Das ist besonders wertvoll für Komponenten-Basestyles, die häufig überschrieben werden sollen, ohne eine Spezifitäts-Kaskade auszulösen.
Ein realer Fall:
/* Basis-Styling */
:where(.button) {
padding: 0.75rem;
background: var(--primary);
}
/* Variante */
.button.is-secondary {
background: var(--secondary);
}
Ohne :where() hätte .button Spezifität 0-1-0, was Varianten (0-1-1) schwieriger macht. Mit :where() bleibt der Basisstil bewusst „schwach“. In größeren Codebases kann das massiv zur Stabilität beitragen.
:has() – Das erste echte „Parent-Selektor”-Feature
:has() ist das vielleicht mächtigste neue Feature in CSS. Es erlaubt Styling basierend auf dem Zustand oder Inhalt von Kind-Elementen. Etwas, das früher nur mit JavaScript möglich war.
Spezifität bei :has()
:has() verhält sich wie :is() – es übernimmt die höchste Spezifität der enthaltenen Selektoren:
.card:has(#featured-image) { ... }
/* Spezifität: 1-1-0 (ID + Klasse) */
.card:has(.thumbnail) { ... }
/* Spezifität: 0-2-0 (zwei Klassen) */
Beispiel 1: Karte mit Bild anders darstellen
.card:has(img) {
padding-top: 0;
}
Früher hätte man dafür eine zusätzliche Klasse gebraucht:
<div class="card card--with-image">...</div>
Mit :has() wird CSS kontextbewusster und Komponenten werden unabhängiger vom Markup.
Beispiel 2: Validierungszustände ohne JS
.form-group:has(input:invalid) {
border-color: var(--error);
}
Das macht kleine Interaktionen deutlich einfacher.
Beispiel 3: Menü öffnen, wenn das Toggle aktiv ist
Mit :has() auf einem gemeinsamen Container kann man ein Menü ohne JavaScript steuern:
<nav class="nav-container">
<input type="checkbox" id="menu-toggle" class="menu-toggle" hidden>
<label for="menu-toggle">Menü</label>
<ul class="menu">...</ul>
</nav>
.nav-container:has(.menu-toggle:checked) .menu {
display: block;
}
Früher wäre das ein Fall für JavaScript gewesen. Heute reicht CSS – vorausgesetzt, Toggle und Menü teilen sich einen gemeinsamen Vorfahren.
Kombinierte Beispiele aus realen Projekten
1. State-driven Styling für eine Accordion-Komponente
Vor CSS :has() konnte man nur Geschwister-Elemente oder Kinder stylen:
.accordion .header[aria-expanded="true"] + .content {
display: block;
}
Mit :has() kann man den Container selbst basierend auf dem Zustand eines Kind-Elements stylen:
.accordion:has([aria-expanded="true"]) {
background: var(--expanded-bg);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
Das ermöglicht visuelle Zustände auf Container-Ebene, ohne zusätzliche Klassen per JavaScript setzen zu müssen.
2. Navigation mit aktiven Links
Viele Systeme nutzen JavaScript oder serverseitige Logik:
nav a.active {
font-weight: 600;
}
Aber mit :has():
nav li:has(a[aria-current="page"]) {
background: var(--highlight);
}
Die Liste – nicht nur der Link – kann so hervorgehoben werden.
3. Formularfeld mit Error State
Früher:
<div class="field field--error">
<input>
</div>
Mit CSS:
.field:has(input:invalid) {
border-color: var(--error);
}
Das ermöglicht komponentennahe Logik, ohne zusätzliche Klassen.
Wann man moderne Selektoren bewusst einsetzen sollte
Sehr sinnvoll:
- Varianten vereinen (
:is()) - Spezifität reduzieren (
:where()) - Komponenten kontextabhängig machen (
:has()) - interaktive Zustände ohne JS (
:has()+ Pseudo-Klassen) - komplexe Komponenten vereinfachen
Vorsichtig einsetzen:
- Tiefe Selektorstrukturen mit
:has() - Spezifität mit
:is()ungewollt erhöhen - sehr große DOM-Bereiche selektieren (Performance beachten)
In der Praxis funktionieren moderne Selektoren sehr performant, solange man sie nicht für extrem breite Selektoren verwendet.
Browser-Support
:is() und :where() werden seit 2021 von allen modernen Browsern unterstützt. :has() kam später hinzu: Safari 15.4 (2022), Chrome 105 (2022), Firefox 121 (Ende 2023). Für Projekte mit Legacy-Browser-Anforderungen sollte man den Support prüfen – insbesondere bei :has().
Fazit
Moderne Selektoren sind einer der Gründe, warum CSS heute flexibler und mächtiger ist als je zuvor. Sie reduzieren den Bedarf an zusätzlichen Klassen, verringern JavaScript-Abhängigkeiten und machen Komponenten robuster und deklarativer.
Wer :is(), :where() und :has() als feste Werkzeuge im Repertoire hat, baut moderne, wartbare UI-Architekturen – genau so, wie CSS heute gedacht ist.
Die nächsten Türchen bauen auf dieser Basis weiter auf.
Kommentare