CSS-Kaskade, Spezifität und Vererbung: Wer gewinnt wann?

CSS-Kaskade, Spezifität und Vererbung: Wer gewinnt wann?

Wenn eine Farbe nicht greift, obwohl die Regel eindeutig im Stylesheet steht, liegt das fast nie am Browser. Irgendeine andere Deklaration gewinnt, weil sie aus einem stärkeren Ursprung kommt, in einer höheren Ebene liegt, spezifischer ist oder später folgt. Wer diese Reihenfolge kennt, braucht kein !important mehr als Notlösung und findet Konflikte in den Entwicklerwerkzeugen in Sekunden.

Die Sortierkriterien der Kaskade

Schon die CSS2-Empfehlung von 1998 beschrieb die Kaskade in wenigen Schritten: Herkunft und Gewicht, dann Spezifität, dann Reihenfolge. Heute legt CSS Cascading and Inheritance Level 5 (Candidate Recommendation vom 13. Januar 2022) die Reihenfolge fest. Der Browser vergleicht zwei Deklarationen für dieselbe Eigenschaft nacheinander nach diesen Kriterien und hört beim ersten Unterschied auf:

  1. Ursprung und Wichtigkeit (!important)
  2. Kontext, etwa Shadow DOM bei Webkomponenten
  3. direkt am Element hängende Stile (Attribut style)
  4. Cascade Layers (@layer)
  5. Spezifität des Selektors
  6. Nähe der Scope-Wurzel bei @scope (aus dem Entwurf Level 6)
  7. Reihenfolge im Quelltext: die spätere Deklaration gewinnt

Wichtig ist die Rangfolge: Spezifität entscheidet erst, wenn Ursprung, Kontext und Ebene gleich sind. Ein Selektor mit drei IDs verliert gegen eine schlichte Klasse, wenn diese in einer späteren Ebene steht.

Ursprünge: Browser, Nutzer, Autor und !important

Jede Deklaration stammt aus einem Ursprung. Das User-Agent-Stylesheet des Browsers sorgt dafür, dass h1 groß und fett ist und Links unterstrichen sind. Nutzerstile kommen von Menschen, die etwa per Erweiterung oder Browsereinstellung eigene Farben und Schriftgrößen erzwingen. Autorenstile sind das CSS der Website. Hinzu kommen Animationen und Übergänge als eigene Ursprünge. Die vollständige Rangfolge laut Spezifikation, von stark nach schwach:

RangUrsprung
1laufende Transitions
2User-Agent mit !important
3Nutzer mit !important
4Autor mit !important
5Animationen (@keyframes)
6Autor normal
7Nutzer normal
8User-Agent normal

Bei normalen Deklarationen schlägt also der Autor den Nutzer, bei !important dreht sich das um. Diese Umkehr war 1998 eine bewusste Änderung gegenüber CSS1, in dem Autoren-!important noch gewann: Wer aus Gründen der Barrierefreiheit große Schrift oder hohe Kontraste braucht, soll sich gegen jede Website durchsetzen können. Für Autoren folgt daraus eine einfache Regel: !important gehört in Hilfsklassen wie .visually-hidden, die nie überschrieben werden sollen, nicht in den Alltag.

Spezifität berechnen

Spezifität besteht aus drei Zählern, geschrieben als ID-Klasse-Typ. Die erste Spalte zählt ID-Selektoren, die zweite Klassen, Attributselektoren und Pseudoklassen, die dritte Typselektoren und Pseudoelemente. Verglichen wird von links nach rechts; ein einziger höherer Wert weiter links entscheidet, egal wie groß die Zahlen rechts davon sind. Elf Klassen schlagen also keine ID. Der Universalselektor *, Kombinatoren und :where() zählen nichts; :is(), :not() und :has() übernehmen die Spezifität ihres spezifischsten Arguments.

SelektorIDKlasseTyp
*000
ul li002
.nav a011
a:hover011
input[type='email']:focus021
nav > ul li.aktiv::before014
.btn.btn020
#kopf .logo img111
:is(#kopf, .fuss) a101
:where(#kopf) a001
:not(.intern) a011

Zwei Details überraschen oft. Die Nähe im DOM spielt keine Rolle: body h1 und html h1 sind gleich spezifisch, es gewinnt die spätere Regel. Und ein Attribut style am Element steht über allen Selektoren, weil es als eigenes Kriterium vor der Spezifität geprüft wird. Im CSS2-Text von 1998 wurden Pseudoelemente übrigens noch gar nicht mitgezählt; heute zählen sie wie Typselektoren. Mehr zu den einzelnen Selektortypen steht im Beitrag über CSS-Selektoren.

Cascade Layers mit @layer

Ebenen lösen das Hauptproblem großer Projekte: Ein Reset, ein Framework und eigene Komponenten konkurrieren über Spezifität, und am Ende wird hochgerüstet. Mit @layer legt man die Rangfolge vorab fest; Spezifität zählt dann nur noch innerhalb einer Ebene. @layer ist laut MDN seit März 2022 in allen großen Browsern verfügbar.

@layer reset, framework, komponenten;



@import url('framework.css') layer(framework);



@layer framework {
  #inhalt a { color: navy; }      /* 1-0-1, aber frühere Ebene */
}



@layer komponenten {
  .btn { color: white; }          /* 0-1-0, gewinnt trotzdem */
}



a { text-decoration-thickness: 2px; }  /* ohne Ebene: schlägt alle Ebenen */

Zwei Regeln muss man kennen. Stile außerhalb jeder Ebene stehen über allen benannten Ebenen. Und bei !important kehrt sich die Ebenenfolge um: Ein wichtiges reset schlägt dann ein wichtiges komponenten, und Ebenen schlagen wichtige Stile ohne Ebene. Die Empfehlung: Fremd-CSS konsequent in Ebenen packen, eigene Komponenten in die letzte Ebene, und !important innerhalb von Ebenen meiden.

@scope und Nähe als Entscheidungskriterium

@scope begrenzt Regeln auf einen DOM-Bereich, optional mit Untergrenze: @scope (.artikel) to (figure) { img { … } } trifft Bilder im Artikel, aber nicht die in Abbildungen. Selektoren darin bekommen keine zusätzliche Spezifität. Kommen zwei Scopes in Konflikt, gewinnt bei gleicher Spezifität der mit der näheren Wurzel, also das innere Thema bei verschachtelten hellen und dunklen Bereichen. Unterstützung laut Can I use: Chrome und Edge ab 118, Safari ab 17.4, Firefox ab 146; MDN führt @scope als Baseline 2026, also neu verfügbar. Für Projekte mit älteren Zielbrowsern sollte es deshalb vorerst nur ergänzend eingesetzt werden.

Vererbung und die Schlüsselwörter inherit bis revert-layer

Manche Eigenschaften werden an Kindelemente weitergegeben, andere nicht. Schrift und Farbe vererben sich (color, font-family, line-height), Rahmen, Abstände und Hintergründe nicht. MDN nennt auf jeder Eigenschaftsseite den Eintrag „Inherited: yes/no“. Vererbt wird immer der berechnete Wert des Elternelements, und jede Regel, die das Kind direkt trifft, schlägt den geerbten Wert, egal wie spezifisch die Elternregel war.

SchlüsselwortErgebnisTypischer Einsatz
inheritWert des Elternelementsfont: inherit für Buttons und Eingabefelder
initialAnfangswert aus der Spezifikationselten sinnvoll, display: initial ergibt inline
unsetwie inherit bei vererbten, sonst wie initialmit all: unset Elemente komplett neutralisieren
revertzurück auf den vorherigen Ursprung, meist das Browser-StylesheetStandardaussehen einer Liste zurückholen
revert-layerzurück auf die darunterliegende EbeneAusnahme von einer Komponentenregel

Der Unterschied zwischen initial und revert wird oft unterschätzt: initial holt den Wert aus der Spezifikation, nicht den des Browsers. Für display ist das inline, auch bei einem div. Wer das Browser-Standardaussehen will, braucht revert.

Vom deklarierten zum tatsächlichen Wert

Die Spezifikation unterscheidet mehrere Stufen. Aus allen deklarierten Werten wählt die Kaskade einen Sieger (kaskadierter Wert). Fehlt einer, greift Vererbung oder der Anfangswert: Das ist der spezifizierte Wert. Der berechnete Wert löst relative Angaben auf, soweit das ohne Layout geht, etwa 1.5em bei 16 Pixel Elternschrift zu 24px. Der verwendete Wert entsteht erst im Layout, etwa wenn width: 50% zu konkreten Pixeln wird. Der tatsächliche Wert ist schließlich das, was das Gerät darstellen kann, zum Beispiel gerundete Pixel.

Praktisch wichtig ist das bei line-height: line-height: 1.5em wird schon beim Elternelement zu Pixeln berechnet, und genau dieser feste Wert wird vererbt. Eine größere Überschrift darin bekommt dann zu engen Zeilenabstand. line-height: 1.5 ohne Einheit vererbt dagegen den Faktor, der für jede Schriftgröße neu gilt.

Typische Fehler und wie die DevTools sie zeigen

Im Bereich Styles der Chrome-DevTools (Firefox und Edge arbeiten ähnlich) stehen alle Regeln, die ein Element treffen, sortiert nach ihrem Rang. Die Darstellung verrät die Ursache:

  • Durchgestrichen: Die Deklaration wurde von einer anderen in der Kaskade überschrieben. Darüber steht die siegreiche Regel.
  • Durchgestrichen mit Warnsymbol: ungültige Eigenschaft oder ungültiger Wert, oft ein Tippfehler wie colour oder eine fehlende Einheit.
  • Blass mit Info-Symbol: gültig, aber wirkungslos, etwa justify-content an einem Element mit display: block.
  • Abschnitte „Inherited from“: geerbte Werte samt Elternelement, von dem sie stammen.
  • Kursiv: Stile des Browsers oder HTML-Attribute, nicht editierbar.

Fährt man mit der Maus über einen Selektor, zeigt Chrome dessen Spezifität an. Ebenen lassen sich über die Schaltfläche für die Cascade-Layers-Ansicht in ihrer Rangfolge anzeigen. Der Reiter Computed listet die berechneten Endwerte; mit „Show all“ auch vererbte Eigenschaften.

Die häufigsten Fehler aus der Praxis: Ein Framework-Stylesheet wird nach dem eigenen CSS geladen und gewinnt bei gleicher Spezifität durch die Reihenfolge. Eine ID im Selektor zwingt alle späteren Anpassungen zu noch stärkeren Selektoren. Und Formularelemente erben die Seitenschrift nicht, weil das Browser-Stylesheet ihnen eigene Schriftangaben gibt; font: inherit löst das. Bei Stapelproblemen mit überlagerten Elementen hilft die Kaskade übrigens nicht weiter, dort geht es um Stacking Contexts, erklärt im Beitrag zu position und z-index.

Quellen