ToolPal
CSS code on a computer screen

CSS-Spezifität endlich verstehen: Warum deine Styles nicht funktionieren

📷 Negative Space / Pexels

CSS-Spezifität endlich verstehen: Warum deine Styles nicht funktionieren

CSS-Spezifität ist einer der häufigsten Stolpersteine im Frontend. Lerne das (A,B,C)-System mit praktischen Beispielen kennen und erfahre, wie du Stilkonflikte ohne !important löst.

13. April 20265 Min. Lesezeit

Es gibt eine besondere Art von Frustration beim CSS-Schreiben: Man setzt eine Regel, lädt den Browser neu — und nichts passiert. Die Regel erscheint in DevTools, aber mit einem Durchstrich. Irgendein anderer Selektor hat gewonnen. Man versteht nicht warum.

Die schnelle Lösung: !important draufklatschen.

Drei Monate später ist das Stylesheet eine Sammlung von !important-Eskalationen, bei der jede neue Regel die vorherige überschreien muss. CSS-Spezifität ist eines dieser Konzepte, die man einmal wirklich durchdenken muss — nicht weil es komplex ist, sondern weil es auf den ersten Blick gegen die Intuition arbeitet.

Das (A, B, C)-System

CSS-Spezifität ist kein einzelner Wert, sondern ein Tripel (A, B, C). Verglichen wird von links nach rechts, wie bei Versionsnummern. Ein Selektor mit (1, 0, 0) schlägt immer einen Selektor mit (0, 99, 99) — egal, wie viele Klassen oder Elemente sich auf der rechten Seite ansammeln.

A — ID-Selektoren #header, #sidebar und alle anderen #id-Selektoren werden in A gezählt. Ein einziger ID-Selektor ergibt (1, 0, 0). Das ist der Hauptgrund, warum moderne CSS-Praxis vom Styling per ID abrät: Der Spezifitätssprung macht späteres Überschreiben sehr schwierig.

B — Klassen, Attribute, Pseudoklassen Klassenselektoren (.nav), Attributselektoren ([type="text"]) und Pseudoklassen (:hover, :focus, :nth-child()) erhöhen B um jeweils 1. :not() zählt dabei die Spezifität seines Arguments.

C — Typselektoren und Pseudoelemente div, p, ul, a und Pseudoelemente wie ::before, ::after landen in C.

Was nicht zählt Der Universalselektor *, Kombinatoren (>, +, ~) und :where() tragen zur Spezifität nichts bei.

Beispiele im Vergleich

/* (0, 0, 1) */
p { color: red; }

/* (0, 1, 0) */
.intro { color: blue; }

/* (0, 1, 1) */
.intro p { color: green; }

/* (1, 0, 0) */
#main { color: orange; }

/* (1, 1, 1) */
#main .content p { color: purple; }

Wenn alle Regeln dasselbe Element treffen, gewinnt (1, 1, 1).

Typische Irrtümer

Längere Selektoren haben mehr Spezifität

Das stimmt nicht. .nav .list .item a:hover sieht detailliert aus, hat aber nur (0, 3, 2). Ein einziges #nav mit (1, 0, 0) schlägt es trotzdem.

Spätere Regeln gewinnen immer

Nur wenn die Spezifität gleich ist. Bei unterschiedlicher Spezifität gewinnt der höhere Wert, unabhängig von der Reihenfolge im Stylesheet.

Das Verhalten von :is(), :not() und :where()

:not() übernimmt die Spezifität seines Arguments. :not(.hidden) hat dadurch (0, 1, 0).

:is() übernimmt die Spezifität des spezifischsten Arguments in der Klammer. :is(#header, .nav, p) ergibt (1, 0, 0) — wegen #header — selbst wenn das gematchte Element nur .nav ist. Das sorgt gelegentlich für böse Überraschungen.

:where() ist immer null. Das macht es ideal für Basisstile in Bibliotheken: Nutzer können diese Stile problemlos überschreiben, ohne Spezifitätskonflikte zu erzeugen.

Praxisbeispiel: Drittanbieter-Styles überschreiben

Klassische Situation in jedem realen Projekt:

/* Bibliotheks-CSS */
.ui-button.ui-button--primary {
  background-color: #0066cc;
}

/* Eigene Überschreibung */
.my-button {
  background-color: #ff5500;
}

Bibliothek: (0, 2, 0). Eigene Regel: (0, 1, 0). Die eigene Regel verliert.

Saubere Lösungsansätze:

  1. Klasse doppeln: .my-button.my-button erhöht auf (0, 2, 0) — ein valider Trick
  2. Elternkontext hinzufügen: .my-app .my-button ergibt ebenfalls (0, 2, 0)
  3. !important: Nur als letztes Mittel, wenn keine andere Option möglich ist

Der CSS-Spezifitätsrechner

Der CSS-Spezifitätsrechner nimmt einen Selektor entgegen und gibt sofort die (A, B, C)-Aufschlüsselung zurück — kein manuelles Zählen mehr nötig.

Wann er besonders nützlich ist:

  • Beim Debuggen: Beide konkurrierenden Selektoren eintippen und auf einen Blick sehen, warum einer verliert
  • Beim Schreiben: Komplexe Selektoren vor dem Commit prüfen
  • Beim Lernen: Selektoren schrittweise verändern und beobachten, wie sich die Zahlen entwickeln

Grenzen des Tools

Für die allermeisten Alltagsselektoren arbeitet der Rechner präzise. Grenzfälle gibt es bei komplex verschachtelten :is()-Ausdrücken und bei Shadow-DOM-Selektoren (::slotted(), ::part()). Für diese Spezialfälle sollte man das Ergebnis mit den Browser-DevTools abgleichen.

CSS mit niedriger Spezifität schreiben

Das Debuggen zu erleichtern ist gut — erst gar keine Spezifitätskonflikte zu produzieren ist besser.

IDs nur als Hooks, nicht für Styling ID-Attribute eignen sich für JavaScript-Referenzen und Seitenanker. Wer sie für CSS-Styling verwendet, zieht eine schwer überschreibbare Barriere ein.

Selektoren kurz halten .nav-link ist besser als nav ul li a.nav-link. Kürzere Selektoren sind nicht nur weniger spezifisch, sie brechen auch seltener, wenn sich die HTML-Struktur ändert.

BEM oder ähnliche Konventionen einsetzen Mit BEM (.block__element--modifier) lassen sich Elemente präzise mit einzelnen Klassen ansprechen, was fast alle Selektoren bei (0, 1, 0) hält.

!important für Utility-Klassen reservieren .hidden, .visually-hidden, .u-text-center — solche Klassen, die immer gewinnen sollen, sind legitime Anwendungsfälle für !important. Alles andere sollte nochmals überdacht werden.

Verwandte Tools


CSS-Spezifität ist eines jener Konzepte, bei denen sich das Verstehen sofort im Alltag auszahlt. Wenn die Regeln erst einmal sitzen — warum (1, 0, 0) gegen (0, 100, 0) gewinnt, was :where() bewirkt, wie :is() die Berechnung verändert — hört man auf, gegen den Browser zu arbeiten, und fängt an, mit ihm zu arbeiten. Der CSS-Spezifitätsrechner hilft dabei, die Zahl schnell zu ermitteln, wenn man sie braucht.

Häufig gestellte Fragen

Artikel teilen

XLinkedIn

Verwandte Beiträge