CSS · Selectors Level 4 · Pseudo-Klassen · Spezifität
CSS Selectors Level 4
:has, :is, :where, :not und relative Selektoren

Die Selektoren-Ebene 4 hat das CSS-Werkzeugkasten grundlegend erweitert. CSS Selectors Level 4 bringt :has als lang ersehnten Eltern-Selektor, :is und :where für kompakte Selektorlisten, ein erweitertes :not und eine neue :nth-child of-Syntax. Diese Selektoren ersetzen viele JavaScript-Workarounds und reduzieren CSS-Redundanz erheblich.

13 Min. Lesezeit :has · :is · :where · :not · :nth-child of · relative Selektoren Chrome 105+ · Safari 15.4+ · Firefox 121+

1. Warum neue Selektoren so viel ändern

CSS-Selektoren waren jahrelang ein Bereich, der sich kaum veränderte. Die Grundlagen — Typselektoren, Klassenselektoren, ID-Selektoren, Kombinator-Selektoren — stammten aus der Frühzeit des Web. Wer komplexere Anforderungen hatte, griff zu JavaScript, erhöhte die Spezifität durch längere Selektoren oder fügte zusätzliche Klassen zum HTML hinzu. CSS Selectors Level 4 ändert das fundamental: Mit :has, :is, :where und dem erweiterten :not werden Selektoren ausdrucksstärker, ohne HTML-Klassen hinzufügen zu müssen. Das verringert Kopplung zwischen HTML-Struktur und Styling erheblich.

Die Auswirkungen in der täglichen Arbeit sind spürbar: Ein Produktgitter, das nur dann ein bestimmtes Layout zeigen soll, wenn es Produkte mit Badges enthält — früher eine JavaScript-Aufgabe. Mit CSS Selectors Level 4 löst .grid:has(.badge) das deklarativ. Formulare, die sich basierend auf ihrem Inhalt stylen sollen; Navigation, die ihr Aussehen ändert, wenn ein bestimmtes Kindkind active ist — all das wird mit den neuen Selektoren zur reinen CSS-Aufgabe.

2. :has — der Eltern-Selektor ist da

Die Pseudo-Klasse :has() ist der am längsten ersehnte Selektor in der CSS-Geschichte. Sie erlaubt es, ein Element basierend auf seinen Nachfahren oder Nachfolgern zu selektieren. figure:has(figcaption) trifft alle <figure>-Elemente, die eine <figcaption> enthalten. Das funktioniert als echter Eltern-Selektor, aber auch als „vorheriges Geschwister"-Selektor: label:has(+ input:required) trifft ein <label>, unmittelbar gefolgt von einem required input. Diese Möglichkeit hat CSS noch nie vorher geboten.

Die Spezifität von :has() richtet sich nach dem spezifischsten Argument in der Klammerliste. :has(.card) hat die Spezifität einer Klasse (0-1-0). :has(h2.title) hat die Spezifität von h2 + Klasse (0-1-1). Das ist intuitiv und konsistent mit dem Verhalten von :is(). In der Praxis: :has() ist kein Performance-Problem in modernen Browsern — die Engines haben spezielle Optimierungen für häufige :has()-Muster. In sehr großen DOMs mit komplexen :has()-Selektoren sollte man jedoch testen.

/* :has — parent selector and sibling context selector */

/* Card with an image gets different padding */
.card:has(img) {
  padding-block-start: 0;
}

/* Form field label turns red when sibling input is invalid */
label:has(+ input:invalid) {
  color: #dc2626;
  font-weight: 600;
}

/* Navigation item is highlighted when a child link is active */
.nav-item:has(> a[aria-current="page"]) {
  background: #ede9fe;
  border-inline-start: 3px solid #7c3aed;
}

/* Grid switches to single column when it has many items */
.product-grid:has(.product-card:nth-child(n + 7)) {
  grid-template-columns: repeat(2, 1fr);
}

/* Figure without figcaption: no bottom margin */
figure:not(:has(figcaption)) {
  margin-block-end: 0;
}

/* Section with a visible heading gets extra top spacing */
.content-section:has(h2:not([hidden])) {
  padding-block-start: 3rem;
}

3. :is — kompakte Selektorlisten

Die Pseudo-Klasse :is() nimmt eine Selektorliste als Argument und trifft Elemente, die mindestens einem der Selektoren in der Liste entsprechen. Der primäre Anwendungsfall ist die Vereinfachung langer, redundanter Selektoren. Statt header h1, header h2, header h3, main h1, main h2, main h3 schreibt man :is(header, main) :is(h1, h2, h3). Das ist nicht nur kürzer, sondern auch leichter wartbar — Ergänzungen erfordern nur eine Stelle im Code.

Die Spezifität von :is() ist die Spezifität seines spezifischsten Arguments — nicht die durchschnittliche oder minimale Spezifität. :is(h1, .title, #headline) hat immer die Spezifität einer ID (1-0-0), egal welches Element tatsächlich getroffen wird. Das kann überraschend sein: Ein h1, das durch :is(h1, #main-title) getroffen wird, erbt die ID-Spezifität 1-0-0. Wer niedrigere Spezifität braucht, sollte :where() verwenden. Diese Spezifitäts-Regel unterscheidet :is() grundlegend von :where().

/* :is — compact selector lists with inherited specificity */

/* Without :is — 6 separate selectors */
header h1, header h2, header h3,
footer h1, footer h2, footer h3 {
  font-family: var(--font-display);
}

/* With :is — 1 selector, same effect */
:is(header, footer) :is(h1, h2, h3) {
  font-family: var(--font-display);
}

/* Error states for multiple form elements */
:is(input, select, textarea):invalid {
  border-color: #dc2626;
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}

/* Focus-visible on all interactive elements */
:is(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid #7c3aed;
  outline-offset: 2px;
  border-radius: 0.25rem;
}

/* Heading margins reset in common containers */
:is(article, section, aside) > :is(h1, h2, h3, h4) {
  margin-block-start: 0;
}

/* Specificity note: :is(h1, #id) has specificity 1-0-0 (the highest argument) */
/* Use :where instead if you need zero specificity */

4. :where — Selektion ohne Spezifität

:where() ist strukturell identisch mit :is() — es nimmt eine Selektorliste und trifft Elemente, die mindestens einem Selektor entsprechen. Der entscheidende Unterschied: :where() hat immer die Spezifität null (0-0-0), egal wie spezifisch die enthaltenen Selektoren sind. Das macht :where() zum idealen Werkzeug für CSS-Reset-Stylesheets, Base-Styles und Design-System-Grundlagen, die leicht überschreibbar sein sollen.

In der Praxis verwendet man :where() überall dort, wo man Styles definieren möchte, die von Autoren ohne Spezifitäts-Eskalation überschrieben werden können. Ein Typografie-Reset: :where(h1, h2, h3, h4, h5, h6) { font-weight: bold; margin: 0; } — jede spätere Regel, auch h2 { font-weight: normal; } mit Spezifität 0-0-1, überschreibt das. :where() und :is() zusammen bieten also eine vollständige Kontrolle über Spezifität in Selektorlisten: :is() erbt die höchste Spezifität, :where() hat immer null.

5. :not — erweitertes Ausschließen

Das :not() aus CSS Selectors Level 4 ist eine erhebliche Erweiterung gegenüber der Level-3-Version. Level 3 erlaubte nur einen einzelnen einfachen Selektor im Argument — kein descendant-Selektor, keine Selektorliste. Level 4 erlaubt eine vollständige Selektorliste mit mehreren, potenziell komplexen Selektoren. :not(.error, .warning, .info) ist jetzt gültig. a:not([href], [aria-hidden="true"]) trifft Links ohne href und ohne aria-hidden="true". Das vereinfacht viele Ausnahme-Regeln, die früher durch Gegenselektion oder erhöhte Spezifität gelöst wurden.

Die Spezifität von :not() folgt denselben Regeln wie :is(): Sie entspricht der Spezifität des spezifischsten Arguments. :not(.active) hat Spezifität 0-1-0. :not(#hero) hat Spezifität 1-0-0. Für nullspezifische Ausschlüsse kann man :not(:where(.active)) kombinieren — das ist zulässig und ergibt null Spezifität für den Ausschluss. Wichtig: :not() kann nicht in sich selbst verschachtelt werden — :not(:not(.active)) ist ungültig.

/* :not Level 4 — selector list arguments and complex selectors */

/* All links except external and anchor links */
a:not([href^="http"], [href^="#"], [href^="mailto:"]) {
  color: #4a1d96;
  text-decoration-color: rgba(124, 58, 237, 0.4);
}

/* All list items except the last one — no trailing divider */
li:not(:last-child) {
  border-block-end: 1px solid #e2e8f0;
}

/* Inputs that are neither disabled nor read-only */
input:not(:disabled, [readonly]) {
  cursor: text;
}
input:not(:disabled, [readonly]):focus {
  border-color: #7c3aed;
}

/* Table rows — skip the header row */
tr:not(:has(th)) {
  transition: background 0.15s ease;
}
tr:not(:has(th)):hover {
  background: #faf5ff;
}

/* Images that are decorative need no alt — hide from assistive tech */
img:not([alt]) {
  outline: 3px solid #dc2626; /* Dev warning: missing alt attribute */
}

6. :nth-child of — gefilterte Kindknoten

Die erweiterte :nth-child of-Syntax von CSS Selectors Level 4 löst ein langes Problem: Das klassische :nth-child(2n) zählt alle Kindknoten, unabhängig vom Typ. Wenn eine Liste aus <li> und <div>-Elementen gemischt ist, liefert li:nth-child(2n) nicht das intuitiv erwartete Ergebnis — es zählt alle Kindknoten, nicht nur die <li>-Elemente. Die neue Syntax :nth-child(2n of li) zählt ausschließlich Kindknoten, die dem angegebenen Selektor entsprechen.

Das ermöglicht Zebra-Striping, das robust gegen gemischte Kindknoten ist, selektierte Reihen in Tabellen mit Gruppen-Zeilen, und Gitter-Layouts, die nur bestimmte Elementtypen erfassen. Die Syntax lautet: :nth-child(An+B of S), wobei S eine Selektorliste ist. :nth-child of ist ebenfalls auf :nth-last-child anwendbar. :nth-child(1 of .featured) trifft das erste Element mit der Klasse .featured unter seinen Geschwistern — unabhängig von anderen Geschwisterknoten.

7. Relative Selektoren und :scope

Relative Selektoren sind ein weiteres Feature von CSS Selectors Level 4, das vor allem in Verbindung mit der JavaScript-API element.querySelectorAll() und CSS @scope Bedeutung gewinnt. Ein relativer Selektor beginnt mit einem Kombinator: > .child, + .next, ~ .sibling. In normalem CSS sind solche Selektoren ungültig — Selektoren müssen mit einem Element oder einer Pseudo-Klasse beginnen. In :has() und in querySelector dagegen sind relative Selektoren gültig.

Die :scope-Pseudo-Klasse aus Selectors Level 4 schafft eine Referenz auf den Kontext-Knoten. In querySelector ist das das aufrufende Element. In @scope-Blöcken ist es der Scope Root. Außerhalb beider Kontexte verhält sich :scope wie :root und ist damit im globalen Stylesheet-Kontext kaum nützlich. Die Kombination von relativen Selektoren, :scope und :has() macht CSS Selectors Level 4 zu einem vollständig ausdrucksfähigen System für Kontext-basiertes Styling.

8. Selektoren im Spezifitäts-Vergleich

Die neuen Pseudo-Klassen aus CSS Selectors Level 4 verhalten sich bei der Spezifitätsberechnung unterschiedlich. :is() und :not() erben die höchste Spezifität aus ihrer Argumentliste. :where() hat immer Spezifität null. :has() erbt wie :is() die Spezifität seines spezifischsten Arguments. Diese Regeln sind konsistent, können aber überraschend sein, wenn man Selektorlisten mit gemischten Spezifitätsstufen verwendet.

Selektor Beispiel Spezifität Level
:is() :is(h1, .title) 0-1-0 (Klasse) Level 4
:where() :where(h1, #id) 0-0-0 (immer null) Level 4
:not() :not(.active, #hero) 1-0-0 (ID) Level 4
:has() :has(.card) 0-1-0 (Klasse) Level 4
:nth-child of :nth-child(2n of .item) 0-1-0 (Klasse) Level 4

Die wichtigste praktische Konsequenz: Wenn man :is() für Selektoren mit gemischten Spezifitäten verwendet, z. B. :is(h2, .heading, #main-title), haben alle gematchten Elemente die Spezifität 1-0-0 — auch ein einfaches h2. Um das zu vermeiden, sollte man Selektoren gleicher Spezifitätsstufe in :is() gruppieren und unterschiedliche Stufen in separate Regeln aufteilen oder :where() für die Nicht-Spezifischen verwenden.

9. CSS Selectors Level 4 in der Praxis

In realen Projekten zeigt sich der Nutzen von CSS Selectors Level 4 besonders bei Form-Styling, Navigation und Content-basierten Layouts. Formulare haben oft komplexe Zustände: Ein Input ist invalid, required, hat Fokus, ist disabled. Mit :is(input, select, textarea):not(:disabled):invalid lässt sich das kompakt ausdrücken. Navigationen zeigen aktive Zustände auf verschiedenen Ebenen — .nav-item:has([aria-current]) fasst das zusammen, ohne den HTML-Zustand duplizieren zu müssen.

Für Magento/Hyvä-Projekte sind diese Selektoren besonders wertvoll in Produktlisten und Checkout-Flows. Produktkarten, die einen Sale-Badge haben, erhalten ein hervorgehobenes Border-Styling über .product-card:has(.badge--sale). Checkout-Schritte zeigen Fortschritt über .step:has(+ .step--active). Tabellarische Produktvergleiche wechseln das Layout mit .compare-table:has(.compare-cell:nth-child(n+4)). CSS Selectors Level 4 macht solche kontextsensitiven Styles zu reiner CSS-Arbeit.

/* CSS Selectors Level 4 — practical combinations */

/* Form validation styles using :has and :is */
.form-group:has(input:invalid:not(:placeholder-shown)) .error-msg {
  display: block;
  color: #dc2626;
}

.form-group:has(input:valid:not(:placeholder-shown)) .success-icon {
  display: inline-flex;
  color: #16a34a;
}

/* Navigation: active section highlighting with :has */
.sidebar-nav .section:has(.nav-link[aria-current="page"]) {
  background: #faf5ff;
  border-inline-start: 3px solid #7c3aed;
}

/* :where for low-specificity base typography */
:where(article, .content, .prose) :where(h2, h3, h4) {
  font-weight: 700;
  line-height: 1.25;
  color: #1e1b4b;
}

/* :nth-child of — zebra striping for mixed DOM */
.data-row:nth-child(odd of .data-row) {
  background: #faf5ff;
}

/* :not with selector list — all links except nav links */
a:not(:is(.nav-link, .breadcrumb-link, .footer-link)) {
  text-decoration: underline;
  text-decoration-color: rgba(124, 58, 237, 0.5);
}

10. Zusammenfassung

CSS Selectors Level 4 hat die Ausdrucksfähigkeit von CSS-Selektoren fundamental erweitert. :has() ist der erste echte Eltern-Selektor, der Styling basierend auf Kindknoten und Nachfolgern ermöglicht. :is() fasst Selektorlisten kompakt zusammen und erbt die höchste Spezifität seiner Argumente. :where() macht dasselbe mit Spezifität null — ideal für überschreibbare Base-Styles. Das erweiterte :not() akzeptiert Selektorlisten und komplexe Selektoren. :nth-child of filtert Kindknoten nach Typ, bevor gezählt wird.

Browser-Support ist 2026 sehr gut: :has(), :is(), :where() und erweitertes :not() laufen in Chrome 105+, Safari 15.4+ und Firefox 121+. :nth-child of hat etwas eingeschränkteren Support, ist aber in allen modernen Browsern nutzbar. Die wichtigste Regel für den produktiven Einsatz: Spezifitätsverhalten von :is(), :not() und :has() verstehen, um unerwartete Kaskaden-Konflikte zu vermeiden. :where() ist der sichere Standardwahl für System-Styles, die leicht überschreibbar sein sollen.

CSS Selectors Level 4 — Das Wichtigste auf einen Blick

:has()

Eltern-Selektor und Vorgänger-Selektor. .card:has(img) trifft Karten mit Bild. label:has(+ input:invalid) trifft Label mit ungültigem Geschwister-Input.

:is() vs. :where()

:is() erbt höchste Spezifität der Argumente. :where() hat immer 0-0-0. Für Base-Styles und überschreibbare Regeln: :where() verwenden.

:not() Level 4

Akzeptiert Selektorlisten: a:not([href], [aria-hidden]). Spezifität = spezifischstes Argument. Kombination mit :where() für nullspezifische Ausschlüsse.

:nth-child of

:nth-child(2n of .item) zählt nur Elemente, die .item entsprechen. Robust für gemischte DOM-Strukturen — Zebra-Striping funktioniert korrekt.

Mironsoft

Modernes CSS, Hyvä-Themes und Magento-Frontend-Entwicklung

CSS-Selektoren, die mit dem Projekt mitwachsen?

Wir modernisieren CSS-Architekturen in Hyvä- und Magento-Projekten — von redundanten Selektoren zu kompakten, wartbaren Regeln mit :has, :is, :where und dem vollen Selectors-Level-4-Arsenal.

Selektor-Audit

Analyse redundanter Selektoren und Modernisierung mit CSS Level 4 — weniger Code, mehr Ausdruckskraft

Form-Styling

Komplexe Formular-States mit :has, :is und :not sauber und ohne JavaScript-Klassen-Toggling umsetzen

Design-System

Base-Styles mit :where und überschreibbare Komponenten-Styles mit :is für skalierbare Systeme

11. FAQ: CSS Selectors Level 4

1Was ist :has() in CSS?
Der Eltern-Selektor. .card:has(img) trifft Karten mit Bild. label:has(+ input:invalid) trifft Labels mit ungültigem Folge-Input. Erster echter Eltern-Selektor in CSS.
2:is() vs. :where()?
:is() erbt höchste Spezifität der Argumente. :where() hat immer Spezifität 0-0-0. Für überschreibbare Base-Styles: :where(). Für normale Selektoren: :is().
3:not() Level 4 vs. Level 3?
Level 4 erlaubt Selektorlisten: a:not([href], [aria-hidden]). Level 3 nur einzelne einfache Selektoren. Spezifität = spezifischstes Argument.
4Was ist :nth-child of?
:nth-child(2n of .item) zählt nur passende Elemente — nicht alle Geschwister. Zebra-Striping und Gruppen-Selektion in gemischten DOMs wird korrekt.
5Warum hat :is(h1, #id) ID-Spezifität?
:is() erbt die Spezifität des spezifischsten Arguments. #id = 1-0-0 — alle durch :is(h1, #id) getroffenen Elemente bekommen 1-0-0. Für nullspezifische Listen: :where().
6Browser-Support?
:has(), :is(), :where(), :not() Level 4 — Chrome 105+, Safari 15.4+, Firefox 121+. Sehr guter Support für Produktionseinsatz 2026.
7Ist :has() performant?
Ja — moderne Browser haben Optimierungen für häufige :has()-Muster. Bei sehr großen DOMs und komplexen Selektoren testen. Einfache Klassenargumente sind unproblematisch.
8:has() in :not() verschachteln?
Ja. figure:not(:has(figcaption)) trifft figure ohne figcaption. Sehr nützlich für kontextsensitives Styling ohne zusätzliche HTML-Klassen.
9Was sind relative Selektoren?
Selektoren, die mit einem Kombinator beginnen: > .child, + .next. In :has() gültig: .item:has(> .badge) trifft .item mit direktem .badge-Kind. Außerhalb von :has() nicht nutzbar.
10:where() für Design-Systeme?
Ja — :where() für alle Base-Styles und Reset-Regeln, die leicht überschreibbar sein sollen. Jede spätere Regel, auch h2 mit 0-0-1, schlägt :where(h2) mit 0-0-0.