mitmario.dev

Spezifität rechnen

CSS Im Browser 4 Min Lesezeit 3 BeispieleLektion 2 von 6

.karte p schlägt p, das war leicht zu sehen. Ob .karte .wichtig gegen #hinweis p gewinnt, siehst du nicht mehr auf einen Blick. Ausrechnen lässt es sich trotzdem, und zwar im Kopf.

Das Tripel

Jeder Selektor bekommt drei Zahlen, in dieser Reihenfolge.

Die erste Zahl zählt IDs. Jedes #etwas zählt einen Punkt.

Die zweite zählt Klassen, Attribute und Pseudoklassen, also .karte, [href], :hover, :first-child. Alle drei Sorten wiegen gleich viel.

Die dritte zählt Elemente und Pseudoelemente, also p, nav, ::before.

Im Alltag ist die nützlichste der drei Regeln, dass Klassen, Attribute und Pseudoklassen in derselben Spalte landen. [href$=".pdf"] und .pdf-link haben exakt dasselbe Gewicht, und li:first-child wiegt genauso viel wie li.erste. Du kannst also zwischen einem Attribut-Selektor und einer Klasse frei wählen, ohne dir die Kaskade zu verbiegen.

Das Tripel an einem Element
/* 0 IDs, 0 Klassen, 1 Element: 0-0-1 */
p {
  color: #2563eb;
}

/* 0 IDs, 1 Klasse, 1 Element: 0-1-1 */
.karte p {
  color: #15803d;
}

/* 0 IDs, 2 Klassen, 0 Elemente: 0-2-0 */
.karte .wichtig {
  color: #b91c1c;
}

Drei Regeln sprechen denselben Absatz an. p ist 0-0-1, .karte p ist 0-1-1, .karte .wichtig ist 0-2-0. Der Absatz ist rot, weil 0-2-0 die höchste Zahl an zweiter Stelle hat.

Nachrechnen musst du das übrigens nur, solange du es lernst. Im Reiter „Regeln“ steht am Absatz hinter jedem Selektor sein Tripel, in derselben Schreibweise wie hier im Text. .karte .wichtig mit 0-2-0 steht oben und gilt, darunter .karte p mit 0-1-1 und p mit 0-0-1, beide durchgestrichen, beide mit dem Vermerk „überstimmt durch höhere Spezifität (0-2-0)“.

Ohne die letzte Regel gewinnt Grün, ohne die mittlere dazu bleibt Blau, und die Liste im Streifen sortiert sich dabei jedes Mal neu.

Die drei Zahlen sind keine Summe. Sie werden der Reihe nach verglichen, und die erste Stelle, an der sie sich unterscheiden, entscheidet alles.

Hundert Klassen schlagen keine ID
/* Eine ID: 1-0-0 */
#hinweis {
  color: #b91c1c;
}

/* Sechs Klassen: 0-6-0
   Verglichen wird von links nach rechts, und links
   steht 1 gegen 0. Die sechs kommen gar nicht zum Zug. */
.a.b.c.d.e.f {
  color: #2563eb;
}

Sechs Klassen aneinandergehängt ergeben 0-6-0. Die ID allein ergibt 1-0-0. An der ersten Stelle steht 1 gegen 0, und damit ist die Sache erledigt, noch bevor jemand die Klassen gezählt hat.

Im Streifen stehen diese beiden Zahlen untereinander, 1-0-0 oben und 0-6-0 darunter, durchgestrichen. Die Sechs ist die größere Zahl und verliert trotzdem, denn verglichen wird Stelle für Stelle von links.

Deshalb sind IDs im Stylesheet unbequem. Überschreiben lassen sie sich später nicht mehr mit Klassen, nur noch mit einer weiteren ID oder mit !important, und beides tue ich mir ungern an.

Was nicht oder anders mitzählt

Drei Sonderfälle und eine Ausnahme solltest du kennen, sonst gibt es Überraschungen.

Der Universalselektor * zählt nichts. Er ist 0-0-0 und damit von jeder anderen Regel überschreibbar. Das macht ihn zum sicheren Werkzeug für globale Grundeinstellungen, und darauf baut der Reset in Abschnitt 4.

:not() und :is() zählen das Argument mit dem meisten Gewicht. :is(h1, .titel) ist 0-1-0, weil .titel von den beiden mehr wiegt.

:where() zählt nichts. Es funktioniert wie :is(), bringt aber die Spezifität null mit. Deshalb steht es in Resets und Bibliotheken, wo eine Regel leicht überschreibbar bleiben soll.

Das style-Attribut läuft gar nicht durch diese Rechnung. Ein style="color: red" direkt am Element ist eine eigene Stufe vor der Spezifität und schlägt jede normale Regel im Stylesheet, egal wie genau ihr Selektor ist. Nur eine Deklaration mit !important kommt noch dagegen an. Das ist einer der wenigen Fälle, in denen !important vertretbar ist, und die anderen kommen in der übernächsten Lektion. Das ist auch der Hauptgrund, warum der Kurs vom Inline-Attribut abrät.

Was nicht oder anders mitzählt
/* :not() zählt sein Argument mit.
   li:not(.aus) ist 0-1-1, nicht 0-0-1. */
li:not(.aus) {
  color: #15803d;
}

/* Steht später und ist trotzdem chancenlos: 0-0-1 gegen 0-1-1.
   Blau bekommt nur der Punkt, den die Regel darüber ausnimmt. */
li {
  color: #2563eb;
}

/* :where() zählt nichts, diese Regel ist 0-0-0. */
:where(h2) {
  color: #b91c1c;
}

/* 0-0-1 schlägt 0-0-0, also gewinnt Schwarz. */
h2 {
  color: #18181b;
}

Zwei der Sonderfälle lassen sich im Beispiel nachrechnen. Der ausgenommene Listenpunkt ist blau, die beiden anderen bleiben grün, denn li:not(.aus) ist 0-1-1 und schlägt das schlichte li mit 0-0-1, obwohl das später steht. Und die Überschrift ist schwarz, weil :where(h2) mit 0-0-0 gegen h2 mit 0-0-1 verliert.

Beides steht so im Reiter „Regeln“, und das ist die schnellste Art, die Sonderfälle zu prüfen, statt sie zu glauben. An einem der grünen Punkte steht li:not(.aus) mit 0-1-1 über dem durchgestrichenen li mit 0-0-1. An der Überschrift steht h2 mit 0-0-1 über dem durchgestrichenen :where(h2) mit 0-0-0. Die drei Nullen sind das ganze :where(). Der h2 darin zählt nicht mit. Sonst stünde da 0-0-1, und die beiden Regeln hätten gleich viel Gewicht. Am ausgenommenen Listenpunkt zeigt sich nebenbei der Unterschied zwischen „verliert“ und „trifft nicht“. Dort gewinnt li, und li:not(.aus) steht blass darunter, weil dieser Punkt eben ausgenommen ist.

Warum lange Ketten eine Schuld sind

Es ist verführerisch, einen Selektor so lange zu verlängern, bis er endlich gewinnt. Das funktioniert immer. Und es ist fast immer die falsche Lösung.

Ein Selektor wie main .inhalt .karte h3.titel ist 0-3-2. Er gewinnt heute, und morgen brauchst du für die Ausnahme davon etwas noch Längeres. So entsteht ein Stylesheet, in dem jede neue Regel mehr Gewicht braucht als alle bisherigen.

Ich erhöhe die Spezifität nur so weit wie nötig. Reicht das nicht, ist die Reihenfolge oder ein Layer das Werkzeug.

In der Aufgabe geht es um so eine Rechnung, an zwei Karten mit je einem Titel.

Zum Mitnehmen

Spezifität sind drei Zahlen, IDs, dann Klassen samt Attributen und Pseudoklassen, dann Elemente. Die erste Stelle, an der sie sich unterscheiden, entscheidet, und :where() und * zählen nichts.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.