Selektoren vereinfachen und Spezifität meistern
Lange Selektorlisten mit denselben Deklarationen sind ein Wartungsproblem jedes größeren Stylesheets. CSS :is() und :where() komprimieren solche Listen zu einer einzigen Regel, mit präziser Kontrolle über die Spezifität – und mit einem Fehlertoleranzverhalten, das klassische Komma-Listen nicht haben.
Inhaltsverzeichnis
- 1. Das Problem mit langen Selektorlisten
- 2. CSS :is() – Syntax und Grundverhalten
- 3. CSS :where() – Spezifität auf null setzen
- 4. Spezifität im Detail: :is() vs. :where() vs. direkt
- 5. Fehlertoleranz und ungültige Selektoren
- 6. Kombination mit :not() und :has()
- 7. :is() in verschachtelten Selektoren
- 8. :is(), :where() und :matches() im Vergleich
- 9. Praxis: Design-Systeme und Reset-Stylesheets
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit langen Selektorlisten
In jedem mittelgroßen Stylesheet findet man Regeln wie h1, h2, h3, h4, h5, h6 { margin-top: 1.5rem; } oder article p, section p, aside p, main p { line-height: 1.75; }. Das ist korrekt und funktional, aber fragil: Wenn ein neues Element zur Liste hinzugefügt werden soll, muss jede solche Regel gefunden und erweitert werden. Wenn ein Selektor in der Liste ungültig ist, ignoriert der Browser in klassischen Komma-Listen die gesamte Regel – auch alle anderen gültigen Selektoren in derselben Liste. Das ist das Fehlerbehandlungsmodell für klassische CSS-Selektorlisten.
CSS :is() und CSS :where() sind die modernen Antworten auf dieses Wartungsproblem. Beide Funktionen nehmen eine Selektorliste als Argument und matchen jedes Element, das auf einen der enthaltenen Selektoren passt. Der entscheidende Unterschied zu einer normalen Komma-Liste liegt in drei Bereichen: der Spezifitätsberechnung, dem Fehlertoleranzverhalten bei ungültigen Selektoren und der Kombinierbarkeit mit anderen Pseudo-Klassen. Diese drei Eigenschaften machen CSS :is() und CSS :where() zu einem fundamentalen Werkzeug moderner CSS-Architektur.
Das Konzept wurde ursprünglich als :matches() in CSS Selectors Level 4 eingeführt und von Safari früh implementiert. Die endgültigen Bezeichnungen :is() und :where() wurden standardisiert, nachdem klar wurde, dass zwei Varianten mit unterschiedlichem Spezifitätsverhalten nötig sind – eine, die die Spezifität der Argumente übernimmt, und eine, die immer Spezifität null hat.
2. CSS :is() – Syntax und Grundverhalten
CSS :is() akzeptiert eine durch Kommas getrennte Selektorliste und matcht jedes Element, das auf mindestens einen der angegebenen Selektoren passt. Die Syntax :is(h1, h2, h3) ist funktional äquivalent zu h1, h2, h3 in einer Kommaliste – mit dem wesentlichen Unterschied, dass die Spezifität von :is() die des spezifischsten Arguments in der Liste ist. Das bedeutet: :is(#id, .class, h1) hat die Spezifität einer ID, weil #id der spezifischste Selektor in der Liste ist.
Der wichtigste Anwendungsfall für CSS :is() ist die Vereinfachung von Selektoren, die denselben Ancestor in verschiedenen Kontexten kombinieren. Statt nav a, header a, footer a schreibt man :is(nav, header, footer) a. Statt article h1, article h2, article h3 schreibt man article :is(h1, h2, h3). Das ist nicht nur kürzer, sondern auch semantisch klarer: die Intention des Selektors ist sofort lesbar, ohne dass man die gemeinsame Struktur mehrerer Selektoren mental rekonstruieren muss.
/* CSS :is() — reducing repetitive selector lists */
/* Before :is(): verbose repetition */
article h1,
article h2,
article h3,
article h4 {
font-weight: 700;
color: #1e1b4b;
}
/* After :is(): single readable rule */
article :is(h1, h2, h3, h4) {
font-weight: 700;
color: #1e1b4b;
}
/* Combining ancestors with :is() */
:is(nav, header, .site-menu) a {
color: #4a1d96;
text-decoration: none;
}
:is(nav, header, .site-menu) a:hover {
color: #7c3aed;
text-decoration: underline;
}
/* Nested headings in any content area */
:is(article, section, .content-block) :is(h2, h3) {
margin-top: 2rem;
margin-bottom: 0.75rem;
line-height: 1.25;
}
3. CSS :where() – Spezifität auf null setzen
CSS :where() verhält sich im Matching identisch zu CSS :is() – es matcht dasselbe Element bei denselben Argumenten. Der einzige und entscheidende Unterschied: die Spezifität von :where() ist immer null, unabhängig davon, wie spezifisch die Selektoren im Argument sind. :where(#id, .class, h1) a hat die Spezifität von a allein, weil der :where()-Teil nichts zur Spezifität beiträgt. Das macht CSS :where() zum idealen Werkzeug für Reset-Stylesheets, Base-Styles und Design-System-Grundlagen.
Der Nutzen wird klar, wenn man über die Cascade nachdenkt: Eine Regel in einem Reset-Stylesheet soll überschreibbar sein, ohne dass Entwickler hohe Spezifität in ihren Komponenten-Styles brauchen. Wenn das Reset :where(h1, h2, h3) { margin: 0; } verwendet, hat diese Regel Spezifität null und kann durch jede Klassen-Regel überschrieben werden, ohne Spezifitätskriege zu riskieren. Das ist das Designprinzip hinter modernen CSS-Resets wie dem von Andy Bell – alle Base-Styles mit CSS :where(), damit Konsumenten volle Kontrolle behalten.
Ein konkretes Beispiel: Tailwind CSS und andere Utility-First-Frameworks kämpfen regelmäßig mit Spezifitätsproblemen, wenn globale Base-Styles und Utility-Klassen aufeinandertreffen. Würden alle Base-Styles mit CSS :where() gesetzt, hätten Utility-Klassen immer Vorrang, da selbst eine einfache Klassenregel eine höhere Spezifität als null hat. Das löst eines der häufigsten Konflikte in solchen Projekten ohne zusätzliche Workarounds.
4. Spezifität im Detail: :is() vs. :where() vs. direkt
Das Spezifitätsmodell von CSS :is() folgt der Regel des spezifischsten Arguments: Die Spezifität der gesamten :is()-Funktion entspricht der höchsten Spezifität unter allen Selektoren in der Argumentliste. Das hat eine wichtige Konsequenz: Wenn man :is(.card, .article, #featured) schreibt, hat der gesamte Selektor ID-Spezifität – auch wenn das Element nur durch .card gematcht wird, nicht durch #featured. Das ist intuitiv für erfahrene CSS-Autoren, kann aber für andere überraschend sein.
CSS :where() hat immer Spezifität null. Das ist keine Einschränkung, sondern ein Feature: Es macht Styles explizit überschreibbar und fördert saubere Cascade-Hierarchien. :where() eignet sich für alles, was als Basis oder Default gedacht ist und niemals einen Override blockieren soll. CSS :is() eignet sich für selektiv angewendete Stile, bei denen die Spezifität der Argumente semantisch relevant ist.
/* Specificity comparison: :is(), :where(), and direct selectors */
/* Specificity: (0,1,0) — .card contributes 1 class */
.card a { color: blue; }
/* Specificity: (0,1,0) — :is() takes the max of (.card) = 1 class */
:is(.card) a { color: blue; }
/* Specificity: (0,0,0) — :where() always contributes 0 */
:where(.card) a { color: blue; }
/* Danger: mixed specificity in :is() list */
/* Entire rule has ID specificity (1,0,0) even if matched by .card */
:is(#hero, .card, .article) h2 {
font-size: 2rem; /* overrides all class-level h2 rules */
}
/* Safe: :where() for base styles — always overridable */
:where(article, section, .prose) p {
line-height: 1.75;
margin-bottom: 1rem;
}
/* Override with any class — wins because :where() is specificity 0 */
.compact p {
line-height: 1.4;
margin-bottom: 0.5rem;
}
5. Fehlertoleranz und ungültige Selektoren
Das Fehlertoleranzverhalten ist einer der am häufigsten übersehenen Unterschiede zwischen CSS :is() und klassischen Kommalisten. In einer normalen Selektorliste wie ::-webkit-input-placeholder, ::placeholder { color: gray; } ignoriert jeder Browser die gesamte Regel, wenn er auch nur einen Selektor nicht kennt. Firefox kennt ::-webkit-input-placeholder nicht und ignoriert damit auch die valide ::placeholder-Regel. Das ist das bekannte Problem mit Vendor-Prefix-Selektoren in Kommalisten.
CSS :is() und CSS :where() verwenden eine fehlertolerante Parsing-Strategie: Ungültige Selektoren in der Argumentliste werden ignoriert, aber die gesamte Regel bleibt gültig. Das bedeutet, man kann zukünftige oder experimentelle Selektoren sicher mit etablierten mischen: :is(.supports-new-feature, :experimental-pseudo) .element funktioniert in Browsern, die :experimental-pseudo nicht kennen, trotzdem korrekt für .supports-new-feature .element. Dieses Verhalten macht schrittweise CSS-Migrationen deutlich sicherer.
Die Grenze der Fehlertoleranz liegt bei forgiving-selectors: Die Spezifikation beschreibt die Argumentlisten von CSS :is() und CSS :where() als "forgiving selector lists". Das Gegenteil – "unforgiving" – gilt für Argumente von :not() in früheren Implementierungen. Modernes :not() akzeptiert ebenfalls Selektorlisten, verhält sich aber je nach Browser-Version unterschiedlich bei ungültigen Argumenten. Das ist ein wichtiger Unterschied, der bei der :not()-Kombination berücksichtigt werden muss.
6. Kombination mit :not() und :has()
Die Kombination von CSS :is() mit :not() ist ein mächtiges Werkzeug für präzise Selektoren. :is(h1, h2, h3):not(.no-margin) matcht alle drei Überschriften-Typen, außer diejenigen mit der Klasse .no-margin. Das ist eleganter als die Alternativen h1:not(.no-margin), h2:not(.no-margin), h3:not(.no-margin). Auch die Umkehrung ist nützlich: :not(:is(h1, h2, h3)) matcht alles außer Überschriften.
Die Kombination mit :has() öffnet noch mächtigere Muster. :is(article, section):has(:is(h2, h3)) matcht alle Artikel und Sektionen, die eine h2 oder h3 enthalten. Das ist ein rein CSS-basierter "parent selector", der früher JavaScript erforderte. In Kombination erlauben CSS :is(), :where(), :not() und :has() eine Selektoren-Sprache, die strukturelle Beziehungen im Dokument direkt ausdrücken kann, ohne HTML-Klassen hinzuzufügen.
/* CSS :is() with :not() and :has() combinations */
/* All headings except those with .decorative class */
:is(h1, h2, h3, h4):not(.decorative) {
font-family: inherit;
font-weight: 700;
}
/* Inverse: style everything that is NOT a heading */
:not(:is(h1, h2, h3, h4, h5, h6)) {
max-width: 70ch;
}
/* :has() + :is(): sections containing any heading level */
:is(article, section):has(:is(h2, h3)) {
padding-top: 2rem;
border-top: 1px solid #e2e8f0;
}
/* Links inside content areas, not inside nav or footer */
:is(article, main, .prose) a:not(:is(nav a, footer a, .btn)) {
color: #4a1d96;
text-decoration: underline;
text-underline-offset: 2px;
}
/* Form inputs: all text-like inputs in one rule */
:is(input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
textarea) {
border: 1px solid #c4b5fd;
border-radius: 0.375rem;
padding: 0.5rem 0.75rem;
}
7. :is() in verschachtelten Selektoren
Mit der nativen CSS-Verschachtelung (&), die inzwischen in allen modernen Browsern unterstützt wird, spielt CSS :is() eine neue Rolle. Innerhalb eines verschachtelten Blocks können Selektoren durch :is() komprimiert werden, ohne den Vorteil der Verschachtelungsstruktur aufzugeben. .card { & :is(h2, h3) { color: purple; } } ist funktional äquivalent zu .card :is(h2, h3) { color: purple; }, aber besser organisiert innerhalb der Komponentenregel.
Ein wichtiges Detail bei der Verschachtelung: Wenn man :is() mit dem impliziten & kombiniert, ergibt sich eine kompakte Schreibweise für Zustandsvarianten. .button { &:is(:hover, :focus-visible) { background: #7c3aed; } } fasst Hover- und Focus-Styles in einer Regel zusammen. Das ist lesbarer als zwei separate Verschachtelungs-Blöcke und macht deutlich, dass beide Zustände dieselben Stile erhalten sollen – eine semantisch klare Ausdrucksweise für interaktive Zustände.
8. :is(), :where() und :matches() im Vergleich
:matches() war der frühere Name für :is() in der CSS-Spezifikation und in Safari. Es ist funktional identisch und wurde durch :is() ersetzt. In Legacy-Code findet man gelegentlich noch :matches(), was in Safari 9–13 der einzige verfügbare Name war.
| Funktion | Spezifität | Fehlertoleranz | Typischer Einsatz |
|---|---|---|---|
:is() |
Max. der Argumente | Ja (forgiving) | Komponenten, Regeln mit bekannter Spezifität |
:where() |
Immer 0 | Ja (forgiving) | Resets, Base-Styles, immer überschreibbar |
| Kommaliste | Individuell pro Selektor | Nein (unforgiving) | Einfache Listen ohne ungültige Selektoren |
:matches() |
Max. der Argumente | Ja | Legacy-Alias für :is(), nur Safari 9–13 |
:not() (modern) |
Max. der Argumente | Teilweise (browserabhängig) | Ausschlüsse, Negationen |
Der wichtigste praktische Unterschied zwischen CSS :is() und Kommalisten liegt bei Vendor-Prefix-Selektoren und zukunftsorientierten Selektoren. In klassischen Kommalisten muss man dieselbe Regel für jeden Prefix in einer separaten Regel wiederholen, weil eine ungültige Deklaration die gesamte Regel ungültig macht. Mit CSS :is() kann man alle Varianten in einem Argument zusammenfassen und muss die Declarations nur einmal schreiben.
9. Praxis: Design-Systeme und Reset-Stylesheets
In modernen Design-Systemen sind CSS :is() und CSS :where() aus der Toolbox nicht mehr wegzudenken. Die Basis-Typografie eines Design-Systems sollte mit CSS :where() gesetzt sein, damit Konsumenten keine Spezifitätsprobleme haben, wenn sie Varianten definieren. Komponentenspezifische Stile, die in einem bestimmten Kontext Gültigkeit haben sollen, werden mit CSS :is() kombiniert, um den Kontext klar auszudrücken.
Ein konkretes Beispiel aus einem Magento-Hyvä-Theme: Das globale Stylesheet setzt Typografie mit :where(h1, h2, h3, h4, h5, h6), sodass jede Tailwind-Klasse wie text-2xl diese Basis ohne Spezifitätsprobleme überschreiben kann. Komponentenspezifische Link-Stile innerhalb von Content-Blöcken werden mit :is(.cms-content, .blog-post, .product-description) a definiert und haben damit höhere Spezifität als allgemeine Link-Resets, aber niedrigere als explizite Modifier-Klassen.
/* CSS :is() and :where() in design system architecture */
/* Base layer: always overridable — specificity 0 */
@layer base {
:where(h1, h2, h3, h4, h5, h6) {
font-family: var(--font-heading);
line-height: 1.25;
font-weight: 700;
}
:where(p, li, dd) {
line-height: 1.75;
max-width: 70ch;
}
:where(a) {
color: inherit;
text-decoration: underline;
text-underline-offset: 2px;
}
}
/* Component layer: :is() — takes specificity of arguments */
@layer components {
:is(.card, .panel, .widget) :is(h2, h3) {
color: #1e1b4b;
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
:is(.prose, .cms-content) a:not(.btn):not([class*="text-"]) {
color: #4a1d96;
text-decoration: underline;
}
}
Mironsoft
CSS-Architektur, Hyvä-Themes und wartbare Stylesheet-Systeme
CSS-Selektoren, die wartbar und spezifitätssicher sind?
Wir entwerfen CSS-Architekturen mit klaren Cascade-Schichten, :where() für überschreibbare Bases und :is() für präzise Komponentenregeln – ohne Spezifitätskriege.
CSS-Audit
Spezifitätsprobleme, redundante Selektorlisten und Cascade-Konflikte analysieren
Selektor-Refactoring
Lange Kommalisten durch :is() und :where() ersetzen und Layer-Architektur aufbauen
Hyvä-Integration
Tailwind und Custom CSS in Magento-Themes konfliktfrei strukturieren
10. Zusammenfassung
CSS :is() und CSS :where() sind zwei der produktivitätssteigernden Pseudo-Klassen-Funktionen aus CSS Selectors Level 4. CSS :is() komprimiert Selektorlisten mit der Spezifität des spezifischsten Arguments – ideal für Komponentenstile, bei denen der Kontext Spezifität tragen soll. CSS :where() macht dasselbe mit Spezifität null – ideal für Reset-Stylesheets, Base-Layers und alle Stile, die in der Cascade immer überschreibbar sein sollen.
Das Fehlertoleranzverhalten beider Funktionen gegenüber ungültigen Selektoren in der Argumentliste macht sie sicherer als klassische Kommalisten bei der Arbeit mit Vendor-Prefixes oder zukunftsorientierten Selektoren. In Kombination mit :not(), :has() und nativer CSS-Verschachtelung entstehen Selektoren, die strukturelle Beziehungen im Dokument präzise ausdrücken, ohne HTML-Klassen zu missbrauchen. Die Grundregel für den Einsatz: CSS :where() für alles, was eine Basis darstellt – CSS :is() für alles, was gezielt überschreiben oder spezifisch sein soll.
CSS :is() und :where() — Das Wichtigste auf einen Blick
:is() Spezifität
Übernimmt die Spezifität des spezifischsten Arguments. :is(#id, .class) hat ID-Spezifität – auch wenn das Element nur durch .class gematcht wird.
:where() Spezifität
Immer null. Ideal für Reset-Stylesheets und Base-Layer: jede Klassenselektor-Regel überschreibt :where()-Styles ohne Spezifitätsprobleme.
Fehlertoleranz
Ungültige Selektoren in der :is()/:where()-Liste werden ignoriert, die Regel bleibt gültig. Klassische Kommalisten werden bei einem ungültigen Selektor vollständig verworfen.
:not() Kombination
:is(h1,h2,h3):not(.no-style) fasst Ausschlüsse kompakt zusammen. :not(:is(...)) schließt eine ganze Gruppe aus – ein Pattern, das zuvor sehr verbose war.