
CSS-Spezifität endlich verstehen: Warum deine Styles nicht funktionieren
📷 Negative Space / PexelsCSS-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.
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:
- Klasse doppeln:
.my-button.my-buttonerhöht auf(0, 2, 0)— ein valider Trick - Elternkontext hinzufügen:
.my-app .my-buttonergibt ebenfalls(0, 2, 0) - !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 zu Tailwind Konverter — bestehendes CSS in Tailwind-Klassen umwandeln
- CSS Minifier — CSS optimieren und minimieren
- CSS Flexbox Generator — Flexbox-Layouts visuell erstellen
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.