Semantische Tokens für den Theme-Wechsel
Theme-Wechsel ist ein Benennungsproblem, bevor es ein CSS-Problem ist. Dark
mode design tokens sind semantische Rollen — background, surface,
text-primary —, bei denen jede Rolle zwei Werte hält, einen pro Theme, und
Komponenten verweisen nur auf den Rollennamen. Wechsle das Theme, und jeder
Verweis wird auf einen Schlag neu aufgelöst; keine Komponente trägt eine Meinung
darüber, in welchem Modus sie sich befindet. Das technische Risiko ist nicht der
Wechsel, sondern das Mapping: die Rollen-zu-Wert-Tabelle eines Dark-Themes ist
kein Spiegelbild der hellen, also muss sie abgeleitet und neu geprüft werden,
niemals umgeklappt.
Der geschichtete Aufbau darunter passt in einen Absatz:
Primitive Tokens benennen das Rohmaterial
(die Stufen der Farbrampe — brand-600, neutral-100), semantische Tokens
benennen UI-Aufgaben und zeigen auf Primitive, und Komponenten konsumieren nur
Semantik. Die Benennungs-Argumente und die Mechanik in der Design-Datei haben
ihre eigenen Guides; dieser Artikel — Teil unseres
Dark-Mode-Guides — handelt davon, was die semantische Ebene
tut, wenn ein zweites Theme dazukommt.
Warum braucht jede Rolle zwei Werte?
Weil die Bedeutung einer Rolle stabil ist und ihr Wert nicht. background
bedeutet in jedem Theme „die Ruhefarbe der Seite”; der Wert, der diese Aufgabe
erfüllt, ist im Light Mode fast weiß und im Dark Mode fast schwarz. Die
semantische Ebene ist die Theme-Grenze: alles darüber — Komponenten, Layouts,
Screens — bleibt theme-blind, und alles darunter ändert sich komplett.
Das ist auch der intuitive Grund, warum das Muster für die Menschen funktioniert, die es benutzen. Wer in Rollen denkt — Designerin oder Entwickler —, fragt wofür ist diese Fläche da, was eine Antwort hat; wer in Werten denkt, fragt welches Grau ist das, was zwei Antworten hat, die von Hand synchron gehalten werden müssen. Namen, die eine Aufgabe statt eines Werts versprechen, machen das zweite Theme zu einer Datenänderung statt zu einem zweiten Design.
Warum ist das Dark-Mapping kein Spiegelbild des hellen?
Das verlockende mentale Modell ist, die Skala umzuklappen — wenn der Light Mode
neutral-100 für die Seite benutzt, nimmt der Dark Mode die Gegennummer. Das
Mapping abzuleiten und zu prüfen liefert etwas Interessanteres. Sechs Rollen aus
einer Palette mit der Ausgangsfarbe #2563eb — oklch(0.546 0.215 262.9):
| Rolle | Light | Dark (abgeleitet) | Was ein reines Spiegelbild sagen würde |
|---|---|---|---|
background | neutral-100 (L ≈0.94) | neutral-900 (L ≈0.22) | neutral-800 — aber die Seite verankert sich am unteren Ende der Skala, damit die Elevations-Leiter darüber Platz hat |
surface (Karte) | neutral-50 (L ≈0.97) | neutral-800 (L ≈0.28) | neutral-900 — was die Karte unter die Seite absinken lassen und die Elevation umkehren würde |
text-primary | neutral-800 (L ≈0.28) | neutral-100 (L ≈0.94) | neutral-100 — hält, obwohl von Hand abgestimmte Themes oft zu stark auf neutral-200 abmildern, was dünn wirkt |
text-secondary | neutral-600 (L ≈0.47) | neutral-300 (L ≈0.78) | neutral-300 — das Spiegelbild hält |
border-subtle | neutral-200, dunkler als die Karte | neutral-700, heller als die Karte | dieselben Stufen — aber die Beziehung kehrt ihre Richtung um |
accent | brand-600 (≈#3E65B5) | ≈oklch(0.70 0.14 262.9) aus der dunklen Farbrampe | brand-300 der hellen Farbrampe — die komplett falsche Rampe |
Drei Asymmetrien sind es wert, benannt zu werden. Erstens kreuzen background
und surface ihre Spiegelbilder: erhöhte Flächen müssen im Dark Mode heller
sein als das, worauf sie liegen, also landet die Karte auf der
Helligkeitsskala über der Seite, obwohl das Spiegeln sie darunter platziert
hätte. Zweitens der Text: neutral-200 auf der dunklen Seite übersteht das
arithmetische Minimum immer noch (≈12:1 gegenüber der Anforderung von 4,5:1),
aber heller Text auf Dunkel wirkt tendenziell dünner, als die Zahlen vermuten
lassen — APCA, das die Polarität modelliert, meldet das Paar niedriger als sein
Pendant im Light-Theme —, also landet die Ableitung eine Stufe heller, als
Vorsicht wählen würde. Drittens stammt der dunkle Wert des Akzents überhaupt
nicht aus der hellen Farbrampe:
eine abgeleitete dunkle Palette folgt ihrer eigenen Helligkeitskurve
mit einem um rund 20 % erhöhten Chroma, weil dunkle Umgebungen die
wahrgenommene Farbigkeit dämpfen.
Die praktische Konsequenz: Die beiden Mappings sollten aus einer Ableitung
entstehen, nicht als zwei Listen gepflegt werden. Der Token-Export von Scale
Composer enthält eine semantic- und eine semantic-dark-Sektion, aus
denselben Ausgangsfarben generiert — jede Rolle pro Theme neu abgeleitet,
WCAG-Mindestwerte neu geprüft, APCA daneben ausgewiesen und onFill-Rollen neu
verifiziert, denn eine Füllung, die im Light Mode weißen Text trug, will im Dark
Mode womöglich dunklen Text.
Öffne den Export mit beiden semantischen Sektionen in Scale Composer — dieselben Rollennamen auf beiden Seiten, die dunklen Werte sichtbar kein Spiegelbild der hellen.

Welche Muster für den Theme-Wechsel scheitern bei Skalierung?
Drei Muster kehren immer wieder, und alle drei scheitern auf dieselbe Weise — das Mapping wird verstreut, statt einmal deklariert.
dark:-Varianten pro Komponente. Tailwinds dark:-Präfix wiederholt den
dunklen Wert an jeder Verwendungsstelle: bg-white dark:bg-gray-900 auf jeder
Karte, in jeder Datei. Für eine kleine Website ist das in Ordnung — das ganze
Mapping passt auf einen Bildschirm. Für ein System ist es eine Wartungssteuer:
zu ändern, was „surface” im Dark Mode bedeutet, ist ein Suchen-und-Ersetzen über
die gesamte Codebasis, und jede Komponente, die ihren dark:-Zwilling verpasst
hat, wird in einem Theme kaputt ausgeliefert.
if (isDark) im Komponenten-Code. Theme-Bedingungen verlagern das Mapping
in die Logik, wo es sich verzweigen, driften und der Prüfung entgehen kann. Eine
Komponente, die fragt, welches Theme aktiv ist, hat eine Verantwortung
übernommen, die die Token-Ebene bereits trägt.
Zwei Stylesheets. Eine dark.css, die neben einer light.css gepflegt
wird, beginnt als Kopie und driftet ab der ersten Änderung, die nur eine Datei
berührt. Nichts erzwingt, dass beide Dateien dieselben Fragen beantworten.
Wie tragen CSS Custom Properties den Wechsel?
Indem sie dieselben Namen unter einem Theme-Scope neu deklarieren. Komponenten verweisen auf die Custom Property; der Wert der Property hängt davon ab, welche Deklaration gerade gilt — die Mechanik sind CSS Custom Properties plus die Kaskade, nicht mehr.
:root {
--color-background: oklch(0.94 0.008 262.9); /* neutral-100 */
--color-surface: oklch(0.97 0.005 262.9); /* neutral-50 */
--color-text-primary: oklch(0.28 0.02 262.9); /* neutral-800 */
--color-accent: oklch(0.546 0.215 262.9); /* Marken-Kern */
}
[data-theme="dark"] {
--color-background: oklch(0.22 0.015 262.9); /* neutral-900 */
--color-surface: oklch(0.28 0.02 262.9); /* neutral-800 */
--color-text-primary: oklch(0.94 0.008 262.9); /* neutral-100 */
--color-accent: oklch(0.70 0.14 262.9); /* dunkle Rampe */
}
.card {
background: var(--color-surface);
color: var(--color-text-primary);
}
Die .card-Regel ist der Punkt: sie erscheint einmal, nennt kein Theme und ist
in beiden korrekt. Der Dark-Block kann ebenso an der Media Query
prefers-color-scheme hängen, oder — das robuste Muster — die Query setzt den
Standard und das Attribut überschreibt ihn; die Verdrahtungs-Details sind
ein eigener Artikel. Welchen Auslöser du
auch wählst, der CSS-Export aus einer Token-Quelle generiert beide Blöcke aus
derselben Ableitung, und genau das hält die beiden Mappings davon ab,
auseinanderzudriften.
Sieh zu, wie sich ein Wechsel auflöst
Dem Mapping vertraut man am leichtesten, nachdem man ihm beim Bewegen zugesehen hat. Schalte die Theme-Vorschau über dem vollständigen Rollensatz um — jede semantische Rolle löst sich auf einen Schlag neu auf, die Asymmetrien aus der Tabelle oben werden direkt sichtbar: die Karte bleibt heller als die Seite, der Akzent springt auf eine hellere Stufe einer neu abgeleiteten Rampe, nirgends Entscheidungen auf Komponentenebene.