Visual Lab · Instrument 2
Spezifitäts-Duell: zwei Selektoren, ein Tupel pro Seite, ein Gewinner
Spezifität ist kein Punktesystem, sondern ein Vergleich von links nach rechts über vier Positionen. Das Instrument zählt für beide Selektoren IDs, Klassen und Typen, legt die Tupel nebeneinander und markiert die Position, an der die Entscheidung fällt.
- Klassen / Attribute / Pseudoklassen .active
- Typen / Pseudoelemente ul, li, a
- IDs #nav
- Typen / Pseudoelemente a
Warum ein Tupel und keine Punktzahl
Ältere Erklärungen rechnen Spezifität als Zahl: 100 für eine ID, 10 für eine Klasse, 1 für einen Typ. Die Rechnung geht, solange keine Position über 9 kommt. Zwölf Typselektoren hintereinander ergäben 12 Punkte und schlügen damit eine Klasse – tatsächlich verlieren sie, weil (0,0,0,12) gegen (0,0,1,0) in der dritten Position entschieden wird, bevor die vierte überhaupt gelesen wird. Deshalb zeigt das Instrument vier getrennte Felder und markiert das Feld, in dem die Entscheidung fällt.
Gleichstand: wenn beide Tupel identisch sind
Zeigt das Instrument „Gleichstand“, hat die Spezifität nichts entschieden, und die Kaskade geht eine Stufe weiter: Es gewinnt die Deklaration, die später im Quelltext steht. Das ist der häufigste Fall in echten Stylesheets – zwei Regeln mit demselben Klassenselektor in zwei Dateien –, und er ist der Grund, warum die Reihenfolge der eingebundenen Stylesheets eine Entscheidung ist und kein Zufall. Wer eine Regel absichtlich vor eine gleich spezifische setzt, verliert; wer sie dahinter setzt, gewinnt, ohne den Selektor anzufassen.
Was gezählt wird – und was nicht
Position 1 steht für den Inline-Stil im style-Attribut. Position 2 zählt ID-Selektoren (#nav). Position 3 zählt Klassen (.active), Attributselektoren ([href]) und Pseudoklassen (:hover, :first-child). Position 4 zählt Typselektoren (a, li) und Pseudoelemente (:first-letter). Nicht gezählt werden der Universalselektor * und alle Kombinatoren.
Verglichen wird von links nach rechts. Sobald eine Position verschieden ist, steht der Gewinner fest; die weiteren Positionen spielen keine Rolle mehr. Deshalb schlägt #nav a mit (0,1,0,1) den Selektor ul li a.active mit (0,0,1,3): In Position 2 steht 1 gegen 0. Dass A drei Typselektoren und eine Klasse hat, zählt nicht mehr.
Zwei Feinheiten, die das Duell mitzählt: Der Universalselektor * und reine Kombinatoren wie > oder + erhöhen keine Position, und bei :not() zählt nicht die Pseudoklasse selbst, sondern ihr Inhalt. Ein li:not(.aktiv) wiegt deshalb genauso viel wie li.aktiv. Der Inline-Stil auf Position 1 lässt sich mit keinem noch so langen Selektor erreichen; nur !important steht darüber.
Die Hintergründe stehen im Artikel Spezifität berechnen; die Stellung der Spezifität in der Kaskade zeigt der Kaskaden-Visualizer.
Was nicht mitzählt
Ebenso wichtig wie die drei gezählten Kategorien ist, was ausdrücklich nicht zählt. Der Universalselektor bringt nichts ein; die Kombinatoren für Nachfahren, Kinder und Geschwister ebenfalls nicht. ul li a und ul > li > a haben deshalb dieselbe Spezifität, obwohl der zweite Selektor deutlich enger trifft. Länge und Genauigkeit eines Selektors sind eben nicht dasselbe wie sein Gewicht.
Die verneinende Pseudoklasse zählt selbst nicht, wohl aber ihr Inhalt: :not(.aktiv) wiegt so viel wie eine Klasse. Bei den logischen Pseudoklassen ist es unterschiedlich – die eine nimmt das stärkste ihrer Argumente, die andere zählt in jedem Fall null. Und die vierte Position, die auf dieser Seite meist unerwähnt bleibt, gehört dem Vermerk für Vorrang: Er steht außerhalb des Tupels und wird vor ihm geprüft. Das Instrument oben lässt ihn deshalb weg, denn eine Deklaration mit Vorrang gewinnt, bevor überhaupt gezählt wird.
Spezifität niedrig halten
Aus der Zählweise folgt eine Arbeitsweise, die sich in großen Stylesheets bewährt hat: möglichst überall dieselbe, niedrige Stufe verwenden – in der Regel genau eine Klasse. Dann entscheidet die Reihenfolge im Quelltext, und die ist sichtbar und leicht zu ändern. Sobald einzelne Regeln höher steigen, beginnt ein Wettlauf, an dessen Ende Selektoren mit Kennungen und der Vermerk für Vorrang stehen, und der Vermerk lässt sich nur noch mit einem weiteren Vermerk schlagen.
Wo eine höhere Stufe unvermeidlich ist – ein fremdes Stylesheet, ein Zustand, der sich durchsetzen muss –, gibt es zwei saubere Wege. Der eine ist die Pseudoklasse, die null zählt und mit der sich Grundregeln bewusst schwach halten lassen. Der andere sind die Kaskadenschichten: Eine später erklärte Schicht schlägt eine frühere unabhängig von der Spezifität, sodass sich Fremdcode und eigene Regeln ohne Wettlauf ordnen lassen. Beide Werkzeuge kamen nach CSS 2.1 und lösen dasselbe Problem an zwei verschiedenen Stellen.
Der Stil am Element selbst
Eine Angabe direkt im Stilattribut eines Elements steht außerhalb des Tupels und schlägt jeden Selektor. Ältere Erklärungen zählen sie als vierte Position mit dem Wert eins; die Wirkung ist dieselbe. Weil ein Skript Stile fast immer auf diesem Weg setzt, lässt sich eine von einem Skript gesetzte Angabe aus dem Stylesheet heraus nur mit dem Vermerk für Vorrang überschreiben – und das ist einer der wenigen berechtigten Gründe, ihn zu verwenden.
Fragen zum Instrument
Zählt :not() zur Spezifität?
:not() selbst nicht, aber sein Argument: p:not(.hinweis) hat (0,0,1,1). CSS 2.1 kennt :not() noch nicht; das Instrument zählt es nach Selectors Level 3.
Warum sind ul > li und ul li gleich spezifisch?
Kombinatoren – Leerzeichen, >, +, ~ – haben kein Gewicht. Beide Selektoren bestehen aus zwei Typselektoren: (0,0,0,2).
Was ist mit dem style-Attribut?
Es steht in der ersten Position des Tupels und schlägt jeden Selektor. Das Instrument vergleicht Selektoren in Stylesheets; ein Inline-Stil hätte (1,0,0,0).