Dreistufige Token-Architektur mit light-dark()
Ein wartbares Dark-Light-Token-System entsteht nicht aus einer einzigen Ebene von Custom Properties, sondern aus drei sauber getrennten Schichten: Primitive Tokens, Semantic Tokens und Component Tokens. Zusammen mit der light-dark() Funktion lässt sich diese Architektur ohne doppelte Media Queries pro Komponente umsetzen.
Inhaltsverzeichnis
- 1. Warum eine einzelne Ebene an Custom Properties nicht reicht
- 2. Die Drei-Ebenen-Architektur: Primitive, Semantic, Component
- 3. Die light-dark Funktion: Umschalten ohne doppelte Media Queries
- 4. Aufbau eines Token-Systems in der Praxis
- 5. Component Tokens: Kapselung pro Komponente
- 6. Governance: Naming Conventions gegen Wildwuchs
- 7. Integration mit Design-Tools und Style Dictionary
- 8. Testing und Dokumentation des Token-Systems
- 9. Token-System im direkten Vergleich zu Alternativen
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine einzelne Ebene an Custom Properties nicht reicht
Viele Projekte starten mit einer einzigen flachen Liste von Custom Properties, etwa --blue: #2563eb oder --red: #dc2626. Das funktioniert für kleine Projekte, wird aber schnell zum Problem, sobald ein Dark-Light-Token-System entstehen soll. Wird --blue direkt in einer Button-Komponente verwendet und soll sich die Bedeutung von Blau im Dark Mode ändern, etwa zu einem helleren Ton für ausreichenden Kontrast, muss diese Änderung überall dort nachvollzogen werden, wo --blue direkt referenziert wird.
Das eigentliche Problem ist die Vermischung zweier unterschiedlicher Konzepte in einer einzigen Variable: der konkrete Farbwert und seine Bedeutung im Interface. Ein robustes Dark-Light-Token-System trennt diese beiden Ebenen bewusst, indem es zwischen der reinen Farbdefinition und ihrer semantischen Verwendung unterscheidet. Diese Trennung ist der Kern jeder skalierbaren Token-Architektur und wird in den folgenden Abschnitten Schritt für Schritt aufgebaut.
2. Die Drei-Ebenen-Architektur: Primitive, Semantic, Component
Ein ausgereiftes Dark-Light-Token-System besteht aus drei klar getrennten Ebenen. Die erste Ebene, Primitive Tokens, enthält ausschließlich rohe Farbwerte ohne jede Bedeutung, etwa --violet-500: oklch(58% 0.19 291). Diese Ebene ändert sich fast nie und bildet die Rohpalette, aus der alle weiteren Ebenen schöpfen.
Die zweite Ebene, Semantic Tokens, gibt den Primitiven eine Bedeutung im Kontext der Anwendung, etwa --color-accent oder --color-danger. Genau auf dieser Ebene findet die Dark-Light-Umschaltung statt: --color-accent zeigt im hellen Modus auf einen dunkleren Primitiv-Ton, im dunklen Modus auf einen helleren, damit der Kontrast in beiden Modi stimmt. Die dritte Ebene, Component Tokens, referenziert wiederum die Semantic Tokens und kapselt Komponenten-spezifische Details, etwa --button-primary-bg: var(--color-accent).
/* Layer 1: Primitive Tokens — raw values, no meaning attached */
:root {
--violet-100: oklch(0.94 0.05 291);
--violet-500: oklch(0.58 0.19 291);
--violet-900: oklch(0.24 0.10 291);
--slate-50: oklch(0.98 0.01 258);
--slate-900: oklch(0.20 0.02 258);
}
/* Layer 2: Semantic Tokens — meaning, switches with the color scheme */
:root {
--color-accent: var(--violet-500);
--color-surface: var(--slate-50);
--color-on-surface: var(--slate-900);
}
@media (prefers-color-scheme: dark) {
:root {
--color-accent: var(--violet-100);
--color-surface: var(--slate-900);
--color-on-surface: var(--slate-50);
}
}
/* Layer 3: Component Tokens — encapsulate per-component usage */
.button--primary {
--button-bg: var(--color-accent);
--button-fg: var(--color-on-surface);
background: var(--button-bg);
color: var(--button-fg);
}
3. Die light-dark Funktion: Umschalten ohne doppelte Media Queries
Die native CSS-Funktion light-dark() vereinfacht die Semantic-Token-Ebene erheblich. Statt jeden Semantic Token in einer separaten prefers-color-scheme Media Query zu überschreiben, lässt sich der helle und dunkle Wert direkt in der Definition angeben: --color-accent: light-dark(var(--violet-900), var(--violet-100));. Voraussetzung ist, dass color-scheme: light dark; auf dem Wurzelelement gesetzt ist, damit der Browser weiß, welchen Modus er aktuell anwenden soll.
Der Vorteil für ein Dark-Light-Token-System mit vielen Semantic Tokens ist erheblich: Statt eine wachsende Media Query mit Dutzenden Überschreibungen zu pflegen, steht jeder Token an einer einzigen Stelle mit beiden Werten nebeneinander, was Code-Reviews vereinfacht und Copy-Paste-Fehler zwischen hellen und dunklen Werten deutlich reduziert, weil beide Werte im selben Blick sichtbar sind.
/* light-dark() collapses two declarations into one, side by side */
:root {
color-scheme: light dark;
--color-accent: light-dark(var(--violet-900), var(--violet-100));
--color-surface: light-dark(var(--slate-50), var(--slate-900));
--color-on-surface: light-dark(var(--slate-900), var(--slate-50));
--color-border: light-dark(oklch(0.9 0.01 258), oklch(0.35 0.02 258));
}
/* No separate @media block needed for the semantic layer anymore */
.card {
background: var(--color-surface);
color: var(--color-on-surface);
border: 1px solid var(--color-border);
}
4. Aufbau eines Token-Systems in der Praxis
Beim Aufbau eines echten Dark-Light-Token-Systems lohnt es sich, mit einer kleinen, überschaubaren Anzahl an Semantic Tokens zu starten, statt sofort jede denkbare Variante zu modellieren. Typische Grundkategorien sind Oberflächenfarben (surface), Textfarben (on-surface, on-accent), Statusfarben (success, warning, danger) und Rahmenfarben (border, divider). Jede dieser Kategorien bekommt genau einen Satz Semantic Tokens, die über light-dark() beide Farbschemata abdecken.
Ein häufiger Fehler beim Aufbau ist, zu früh zu viele Abstufungen einzuführen, etwa --color-surface-1 bis --color-surface-9, bevor überhaupt ein realer Bedarf für so viele Stufen entstanden ist. Ein Dark-Light-Token-System wächst am gesündesten organisch, das heißt neue Tokens werden erst dann hinzugefügt, wenn eine konkrete Komponente sie tatsächlich benötigt, statt im Voraus eine vollständige, aber größtenteils ungenutzte Token-Bibliothek zu entwerfen.
5. Component Tokens: Kapselung pro Komponente
Die dritte Ebene eines Dark-Light-Token-Systems, die Component Tokens, existiert, um Komponenten unabhängig von Änderungen an der Semantic-Ebene zu machen. Statt dass eine Button-Komponente direkt var(--color-accent) referenziert, definiert sie zunächst eigene, lokal gescopte Tokens wie --button-bg und --button-fg, die anfangs auf Semantic Tokens zeigen, aber pro Instanz überschrieben werden können.
Dieser zusätzliche Indirektionsschritt zahlt sich vor allem bei Varianten aus: Ein .button--danger überschreibt lediglich --button-bg mit var(--color-danger), ohne die zugrunde liegende Regel für Padding, Border-Radius oder Typografie zu duplizieren. Component Tokens machen ein Dark-Light-Token-System damit erst wirklich wiederverwendbar über viele Komponenten-Varianten hinweg, ohne dass jede Variante ihre eigene komplette CSS-Regel benötigt.
/* Component tokens: local indirection, overridable per variant */
.button {
--button-bg: var(--color-accent);
--button-fg: var(--color-on-surface);
background: var(--button-bg);
color: var(--button-fg);
padding: 0.5rem 1.25rem;
border-radius: 0.5rem;
}
.button--danger {
--button-bg: var(--color-danger);
--button-fg: var(--color-on-danger);
}
.button--ghost {
--button-bg: transparent;
--button-fg: var(--color-accent);
}
6. Governance: Naming Conventions gegen Wildwuchs
Ohne klare Namenskonvention entwickelt sich ein Dark-Light-Token-System schnell zu einem unübersichtlichen Sammelsurium widersprüchlich benannter Variablen. Bewährt hat sich ein Präfix-Schema nach Ebene: --{ebene}-{kategorie}-{variante}, etwa --color-surface-raised für einen erhöhten Oberflächenton oder --color-border-focus für einen Fokus-Rahmen. Diese Konsistenz macht es neuen Teammitgliedern leicht, vorhandene Tokens zu finden, statt versehentlich Duplikate mit leicht anderem Namen anzulegen.
Ein weiterer Governance-Baustein ist eine dokumentierte Liste erlaubter Semantic Tokens, gegen die neue Pull Requests geprüft werden. Wird ein neuer Farbwert benötigt, muss zuerst geklärt werden, ob ein bestehender Semantic Token wiederverwendet werden kann, bevor ein neuer angelegt wird. Diese Disziplin verhindert, dass ein Dark-Light-Token-System über die Zeit auf hunderte kaum unterscheidbare Farbtöne anwächst, die de facto niemand mehr überblickt.
7. Integration mit Design-Tools und Style Dictionary
In größeren Organisationen leben Design Tokens oft zunächst in Figma oder einem ähnlichen Design-Tool und müssen von dort in CSS Custom Properties überführt werden. Tools wie Style Dictionary lesen eine plattformunabhängige Token-Definition, meist als JSON, und generieren daraus automatisch die passenden CSS-Dateien inklusive der drei Ebenen eines Dark-Light-Token-Systems. Das stellt sicher, dass Design und Code niemals auseinanderlaufen, weil beide aus derselben Quelle generiert werden.
Wichtig bei dieser Integration ist, die Drei-Ebenen-Struktur auch im Style-Dictionary-Schema abzubilden, statt Primitive und Semantic Tokens in einer einzigen flachen Liste zu vermischen. Nur so bleibt die Transformation zwischen Design-Tool und generiertem CSS nachvollziehbar, und ein Dark-Light-Token-System lässt sich auch bei automatisierter Generierung konsistent weiterentwickeln.
8. Testing und Dokumentation des Token-Systems
Ein Dark-Light-Token-System sollte in einem Storybook oder einer vergleichbaren Style-Guide-Umgebung dokumentiert werden, mit einer visuellen Übersicht aller Semantic Tokens in beiden Farbschemata nebeneinander. Diese Übersicht macht auf einen Blick sichtbar, wenn ein Token im Dark Mode versehentlich denselben Wert wie im Light Mode behält, obwohl eine Anpassung nötig gewesen wäre, oder wenn ein neu hinzugefügter Token gegen die etablierte Namenskonvention verstößt.
Automatisierte Snapshot-Tests, die Komponenten einmal im hellen und einmal im dunklen Modus rendern und die berechneten Farbwerte vergleichen, decken Regressionen zuverlässig auf, wenn sich ein Primitive Token ändert und diese Änderung unerwartet durch alle drei Ebenen des Dark-Light-Token-Systems propagiert. Solche Tests sind besonders wertvoll bei größeren Refactorings der Primitive-Ebene, die theoretisch überall im System Auswirkungen haben können.
9. Token-System im direkten Vergleich zu Alternativen
Die folgende Tabelle vergleicht drei verbreitete Ansätze für Farbverwaltung in größeren Projekten.
| Kriterium | Flache Custom Properties | Dreistufiges Token-System | Sass-Variablen |
|---|---|---|---|
| Dark Mode zur Laufzeit | Eingeschränkt möglich | Voll unterstützt | Nicht möglich |
| Skalierbarkeit | Gering | Hoch | Mittel |
| Komponenten-Isolation | Fehlt | Ja, über Component Tokens | Manuell |
| Design-Tool-Integration | Aufwendig | Gut, mit Style Dictionary | Aufwendig |
| Einstiegsaufwand | Gering | Mittel | Mittel |
Für kleine Projekte reicht eine flache Liste an Custom Properties völlig aus. Sobald aber mehrere Themes, viele Komponenten-Varianten oder eine Anbindung an ein Design-Tool ins Spiel kommen, zahlt sich der etwas höhere Einstiegsaufwand eines dreistufigen Dark-Light-Token-Systems durch spürbar geringeren Wartungsaufwand über die Projektlaufzeit aus.
Mironsoft
Design-Token-Architektur, Design-System-Beratung und Frontend-Governance
Ein Token-System, das mit dem Projekt mitwächst?
Wir bauen dreistufige Design-Token-Systeme mit Custom Properties und light-dark(), inklusive Naming Conventions, Style-Dictionary-Anbindung und Dokumentation, die neue Teammitglieder sofort produktiv macht.
Token-Architektur
Primitive, Semantic und Component Tokens sauber getrennt aufbauen
Governance
Naming Conventions und Review-Prozesse gegen Token-Wildwuchs
Design-Tool-Anbindung
Style Dictionary Pipeline zwischen Figma und CSS Custom Properties
10. Zusammenfassung
Ein tragfähiges Dark-Light-Token-System entsteht aus drei sauber getrennten Ebenen: Primitive Tokens als reine Farbwerte, Semantic Tokens für die Bedeutung im Interface, und Component Tokens für die Kapselung pro Komponente. Die native light-dark() Funktion vereinfacht die Semantic-Ebene erheblich, weil helle und dunkle Werte nebeneinander in einer einzigen Deklaration stehen, statt über wachsende Media Queries verteilt zu sein.
Governance durch klare Naming Conventions und eine dokumentierte Token-Liste verhindert, dass ein Dark-Light-Token-System über die Zeit unübersichtlich wird, während eine Anbindung an Style Dictionary Design und Code synchron hält. Wer diese Bausteine kombiniert, erhält ein System, das mit jeder neuen Komponente robuster wird, statt mit jeder neuen Farbe fragiler.
Custom Properties als Dark-Light-Token-System — Das Wichtigste auf einen Blick
Drei Ebenen
Primitive Tokens für Rohwerte, Semantic Tokens für Bedeutung, Component Tokens für Kapselung.
light-dark() Funktion
Beide Farbschemata in einer Deklaration, kein wachsendes Media-Query-Set nötig.
Governance
Klare Naming Convention und dokumentierte Token-Liste gegen Wildwuchs.
Tooling
Style Dictionary hält Design-Tool und CSS Custom Properties synchron.