Design-Systeme sind 2026 keine Kür mehr, sondern Pflicht. Aber viele Teams scheitern nicht an der Konzeption, sondern an der Umsetzung: Tokens leben im Figma, werden im Code aber als Hex-Codes hartkodiert. Änderungen im Design spiegeln sich nicht automatisch im Produkt. Nach sechs Monaten ist der Drift so groß, dass niemand mehr weiß, welche Farbe in welchem Kontext "korrekt" ist.
Die Gretchenfrage: Wie kommen Tokens ins CSS?
Es gibt drei gängige Ansätze:
- SCSS-Variablen: Tokens werden beim Build in SCSS-Variablen kompiliert. Nachteil: Kein Runtime-Theming möglich. Farbwechsel zur Laufzeit bedeutet Rebuild.
- JavaScript-Design-Tokens: Tokens werden als JS-Objekt exportiert und per CSS-in-JS ins Styling gebracht. Nachteil: Performance, komplexe Tooling-Chain, Framework-Lock-in.
- CSS-Custom-Properties: Tokens werden als
--brand-primary: #0F182Dgesetzt und überall pervar(--brand-primary)referenziert.
Wir haben uns für Ansatz 3 entschieden. CSS-Custom-Properties sind inzwischen in allen relevanten Browsern unterstützt, erlauben Runtime-Theming, sind framework-agnostisch und haben keine Build-Tool-Abhängigkeit.
Unser Token-Schema
Wir pflegen für jede Marke eine brand.json mit der folgenden Struktur:
{
"colors": {
"primary": "#0F182D",
"primaryStrong": "#C8192E",
"secondary": "#2F4E6F",
"secondaryStrong": "#000000",
"surface": "#ffffff",
"surfaceAlt": "#f4f6f9",
"text": "#0F182D",
"muted": "#647080",
"custom1": "#6366f1",
"custom2": "#8b5cf6"
},
"theme": {
"radius": "8px",
"radiusSmall": "4px",
"spacing": "1rem"
},
"typography": {
"fontSans": "Inter, sans-serif",
"fontDisplay": "Space Grotesk, sans-serif"
}
}
Ein Build-Schritt erzeugt daraus eine brand.css mit CSS-Custom-Properties:
:root {
--brand-primary: #0F182D;
--brand-primary-strong: #C8192E;
/* ... */
--brand-primary-a10: color-mix(in srgb, #0F182D 10%, transparent);
--brand-primary-a50: color-mix(in srgb, #0F182D 50%, transparent);
}
Interessant sind die abgeleiteten Werte: Alpha-Varianten (-a10, -a50) werden per color-mix generiert. Das spart uns, jede Transparenz-Stufe manuell zu pflegen.
Der ct-Prefix: Component-Token
Im Styling selbst nutzen wir niemals Raw-Tokens. Stattdessen gibt es Component-Tokens mit dem Prefix ct-:
.c-button[data-variant="primary"] {
background: var(--ct-button-primary-bg, var(--brand-primary));
color: var(--ct-button-primary-text, #ffffff);
}
Der Vorteil: Wenn eine Marke einen abweichenden Button-Style braucht, überschreibt sie --ct-button-primary-bg — ohne --brand-primary global zu ändern und damit andere Komponenten unbeabsichtigt zu betreffen.
Der Editor spricht dieselbe Sprache
Wir haben den Content-Editor so gebaut, dass er beim Farbpicker direkt Token-Namen anzeigt (ct-primary, ct-secondary-strong), nicht Hex-Codes. Das verhindert, dass Redakteure versehentlich nicht-token-basierte Farben eintragen.
Im gespeicherten Content steht dann z.B. { "bg": { "type": "color", "src": "ct-primary" } } — der Renderer löst das zur Laufzeit gegen die aktuelle brand.css auf. Ein Markenwechsel auf derselben Seite wäre damit theoretisch möglich.
Lessons Learned
- Token-Schema früh einfrieren. Nachträgliche Umbenennungen sind schmerzhaft, weil sie durch den gesamten Content-Bestand gehen.
- Alpha-Varianten nicht selbst pflegen.
color-mixist mächtiger als jede manuell definierte Palette. - Typografie-Tokens auch. Font-Sizes, Line-Heights, Letter-Spacing — wenn's ein Token sein kann, mach ein Token draus.
- Abweichungen explizit machen. Wenn eine Marke im Button etwas anderes braucht, dann per Component-Token-Override — nicht per Selector-Spezifität oder per Class-Multiplikation.
Ausblick
Nächstes Thema ist Motion-Tokens: Animations-Dauern und Easing-Kurven zentralisiert pflegen, damit alle Komponenten die gleiche "Stimme" haben. Das ist seltener relevant als Farbe und Typografie, aber für premium-orientierte Marken macht es einen sichtbaren Unterschied.