Das erste Designsystem, das ich gebaut habe, ging mit genau einem Fehler in Produktion, der danach alles heimsuchte: Wir verwendeten blue-500 direkt im Komponentencode. Buttons waren blue-500. Links waren blue-500. Der Fokus-Ring war blue-500. Es fühlte sich sauber und DRY an. Dann wechselte das Marketing das Branding von Blau zu Petrol, und wir verbrachten zwei Sprints damit, jeder buchstäblichen Hex-Referenz über vierzig Repositories hinweg nachzujagen – und übersahen trotzdem ein paar, weil einige als #3B82F6 fest verdrahtet waren statt als Variable. Diese ganze Episode hätte eine einzeilige Änderung sein können. Der Grund, warum sie es nicht war, ist der gesamte Grund, warum es Farb-Tokens für Designsysteme gibt.
Ein Farb-Token ist nur eine benannte Referenz auf einen Farbwert. Aber der Wert von Tokens liegt nicht in der Benennung – er liegt in den Schichten der Benennung und in der Disziplin darüber, welche Schicht eine Komponente berühren darf. Triff die Schichtung richtig, und ein Rebranding, ein Dark-Mode-Launch oder ein Kontrast-Fix wird zu einer kleinen, eingegrenzten Änderung. Triff sie falsch, und du bekommst die Vierzig-Repo-Aufräumaktion.
Drei Schichten: primitiv, semantisch, Komponente
Das mentale Modell, das sich über jedes System hinweg bewährt hat, an dem ich gearbeitet habe, ist eine dreistufige Hierarchie. Jede Stufe referenziert die darunterliegende, und Komponenten dürfen ausschließlich die oberste Stufe konsumieren.
- Primitive Tokens (auch Basis- oder globale Tokens genannt) sind deine rohe Palette.
blue-500: #3B82F6,gray-900: #111827,red-600: #DC2626. Sie beschreiben, was die Farbe ist, und nichts darüber, wo sie verwendet wird. Ein Primitiv sollte niemals direkt im CSS eines Buttons auftauchen. - Semantische Tokens beschreiben die Absicht – die Aufgabe, die die Farbe erfüllt.
color-text-primary,color-bg-surface,color-border-default,color-action-primary,color-feedback-error. Ein semantisches Token verweist auf ein Primitiv:color-action-primary → blue-600. - Komponenten-Tokens sind optional und beschreiben einen bestimmten Teil einer bestimmten Komponente.
button-primary-bg,card-border-color,input-focus-ring. Sie verweisen auf semantische Tokens, nicht auf Primitive.
Hier ist die Regel, die das Ganze zum Funktionieren bringt, und die, die die meisten Teams überspringen: Komponenten referenzieren semantische oder Komponenten-Tokens, niemals Primitive. In dem Moment, in dem ein Button an der semantischen Schicht vorbeigreift, um direkt blue-500 zu schnappen, hast du das Vierzig-Repo-Problem wieder eingeführt, eine Komponente nach der anderen.
Warum überhaupt die Mühe mit der semantischen Zwischenschicht? Weil sie die Naht ist, an der das Theming geschieht. Wenn du auf den Dark Mode umstellst, malst du nicht jede Komponente neu – du lenkst ein paar Dutzend semantische Tokens auf andere Primitive um. Die Komponenten wissen nicht, dass sich etwas geändert hat. Diese Indirektion ist die wirkungsvollste Einzelentscheidung im gesamten System.
Baue die Skala, bevor du irgendetwas benennst
Vor den Semantiken brauchst du Primitive, auf die es sich zu verweisen lohnt – und das bedeutet eine numerische Skala, konventionell 50 bis 950. Die von Tailwind populär gemachte Konvention (50, 100, 200 … 900, 950) ist praktisch zum Industriestandard geworden, und es lohnt sich, sie zu übernehmen, schon damit neue Entwickler sich zuhause fühlen. 50 ist die hellste Tönung, 500 ist ungefähr der reine Markenfarbton, 950 ist nahezu schwarz.
Der Fehler, den Einsteiger machen, ist, eine Skala zu erzeugen, indem sie naiv in sRGB aufhellen und abdunkeln – Weiß für Tönungen hinzufügen, Schwarz für Schattierungen. Du bekommst matschige Mitteltöne und Stufen, die sich nicht gleichmäßig anfühlen, weil wahrgenommene Helligkeit in RGB nicht linear ist. Eine 400 sieht am Ende fast identisch aus wie eine 500, während 700 zu 800 eine Klippe ist. Die Lösung besteht darin, deine Stufen in einem perzeptuellen Modell zu spreizen. Tools, die in HSL arbeiten, bringen dich den größten Teil des Wegs; Tools, die OKLCH verwenden (inzwischen gut in CSS unterstützt), bringen dich den Rest, weil OKLCH die wahrgenommene Helligkeit konsistent hält, während sich der Farbton ändert. Wenn du beim Prototyping einer Skala schnell harmonische Tönungen und Schattierungen nach Augenmaß sehen willst, ist der Farbpaletten-Generator ein schneller Weg, die Beziehungen zwischen Farbtönen zu sehen, bevor du sie in Tokens festlegst.
Ein paar hart erarbeitete Regeln für Skalen:
- Verankere deine Skala am Kontrast, nicht an der Ästhetik. Entscheide früh, welche Stufe deine "Text auf Weiß"-Farbe ist, und prüfe, dass sie die Verhältnisse trifft, die du brauchst. In den meisten Neutralrampen ist
600oder700die Stelle, an der du 4.5:1 gegenüber Weiß überschreitest. Das zu wissen erlaubt dir, semantische Regeln wie "Fließtext verwendetgray-700" mit Zuversicht zu schreiben. - Halte über jeden Farbton dieselbe Anzahl an Stufen. Wenn Blau ein
950hat und Grün bei900aufhört, werden deine semantischen Mappings unregelmäßig und der Dark Mode wird zum Sonderfall-Albtraum. - Übergeneriere nicht. Elf Stufen pro Farbton sind reichlich. Ich habe Teams Zwanzig-Stufen-Skalen ausliefern sehen, in denen die Hälfte der Stufen visuell ununterscheidbar ist und niemand je
425verwendet.
Benennung: beschreibe die Aufgabe, nicht das Aussehen
Das mit Abstand wichtigste Benennungsprinzip ist dieses: Semantische und Komponenten-Tokens sollten den Zweck benennen, niemals das Aussehen. Ein Token namens color-text-red zerbricht an dem Tag, an dem Fehlertext orange wird – der Name lügt dann. Ein Token namens color-feedback-error übersteht diese Änderung unangetastet, weil "error" weiterhin wahr ist, egal zu welchem Rot (oder Orange) es sich auflöst.
Eine Benennungsstruktur, die skaliert, liest sich von der allgemeinen Kategorie zum spezifischen Modifikator, weil sie sich sauber sortieren und auditieren lässt:
category-property-variant-statecolor-bg-surface,color-bg-surface-raised,color-bg-surface-sunkencolor-action-primary,color-action-primary-hover,color-action-dangercolor-text-primary,color-text-secondary,color-text-disabled,color-text-on-action
Das letzte – color-text-on-action – ist ein Token, das die Leute vergessen und bereuen. Wenn deine Aktionsfarbe ein gesättigtes Blau ist, muss Text auf diesem Blau weiß oder nahezu weiß sein, und diese Kombination hat nichts mit color-text-primary zu tun. Mach daraus ein explizites Token, oder du wirst zusehen, wie Entwickler #FFFFFF auf Buttons fest verdrahten und dann Barrierefreiheits-Bugs melden, wenn jemand eine hellgelbe "Warn"-Aktion einführt.
Bei Primitiven ist es richtig und erwartet, das Aussehen zu benennen – blue-500 sollte beschreiben, was es ist, denn genau das ist der Sinn der Primitiv-Schicht. Die "beschreibe die Absicht"-Regel gilt oberhalb davon.
Umsetzung mit CSS Custom Properties
CSS Custom Properties sind das natürliche Zuhause für Tokens, weil sie kaskadieren und vom Kontext überschrieben werden können – was genau die Funktionsweise von Theming ist. Das Muster sind zwei Variablenschichten: Primitive, einmal am Wurzelelement definiert, und Semantiken, am Wurzelelement definiert und unter einem Theme-Selektor neu definiert.
Definiere Primitive global – --blue-600: #2563EB, --gray-900: #111827 und so weiter. Bilde dann die Semantiken darauf ab: --color-action-primary: var(--blue-600) und --color-text-primary: var(--gray-900). Komponenten lesen ausschließlich die semantischen Variablen: color: var(--color-text-primary). Sie benennen nie ein Primitiv und nie einen Hex-Code.
In Tailwind v4 bildet sich das sauber auf die neue @theme-Direktive ab, die sowohl die Tokens deklariert als auch Utilities aus ihnen generiert und jedes Token als Laufzeit-CSS-Variable bereitstellt. Du definierst --color-text-primary: var(--color-gray-900) innerhalb von @theme, verwendest text-primary im Markup und überschreibst die Variable unter einem .dark-Selektor in deiner Base-Layer. Die offizielle Tailwind-Dokumentation zu Theme-Variablen deckt die Mechanik ab, und dieselbe Zwei-Schichten-Disziplin gilt unabhängig vom Framework.
Dark Mode ist ein Token-Mapping-Problem, kein Farbproblem
Der Grund, warum sich die semantische Schicht auszahlt, ist, dass der Dark Mode beinahe trivial wird, sobald sie existiert. Du fasst keine einzige Komponente an. Du überschreibst die semantischen Tokens innerhalb eines .dark-Bereichs (oder [data-theme="dark"]), sodass sie sich zu anderen Primitiven auflösen.
Im Light Mode ist --color-bg-surface: var(--white) und --color-text-primary: var(--gray-900). Im Dark Mode, unter dem .dark-Selektor, ist --color-bg-surface: var(--gray-900) und --color-text-primary: var(--gray-100). Jede Komponente, die color-bg-surface liest, kippt automatisch, weil die Kaskade die Arbeit erledigt.
Zwei Dinge, die hier Probleme bereiten:
- Invertiere nicht einfach die Skala. Reinweißer Text auf reinem Schwarz (
#000auf#FFFinvertiert) ist hart und verursacht Halation – dieses verschmierte Leuchten um Text auf OLED-Bildschirmen. Dunkle Flächen sollten ein dunkles Grau wie#121212bis#1A1A1Asein, und dein hellster Text ein sanftes Off-White statt#FFFFFF. Deshalb braucht deine Skala ein echtes950und50statt buchstäblichem Schwarz und Weiß an den Enden. - Entsättige Akzente für dunkle Hintergründe. Ein
blue-600, das auf Weiß selbstbewusst aussieht, kann auf einer nahezu schwarzen Fläche unangenehm vibrieren. Dark Themes lassen ihr Aktions-Token meist auf eine hellere, leicht weniger gesättigte Stufe verweisen –blue-400stattblue-600. Weil das eine einzige Token-Umlenkung ist, kannst du es global in Sekunden abstimmen.
Die Stolperfallen, die im Maßstab tatsächlich zerbrechen
Nach genügend Rebrandings und Theme-Launches reimen sich die Fehlerbilder:
- Primitive in Komponenten durchsickern lassen. Die Ursünde. Linte dagegen – eine Regel, die primitive Token-Namen und rohe Hex-Werte in Komponentendateien verbietet, ist es wert, an Tag eins geschrieben zu werden.
- Semantische Tokens, die heimlich einen Wert kodieren.
color-blue-actionist ein Primitiv im semantischen Kostüm. Wenn der Name einen Farbton enthält, ist er nicht wirklich semantisch und wird beim Rebranding zerbrechen. - Die semantische Schicht überspringen, "um schnell zu sein". Komponenten, die direkt an Primitive gebunden sind, fühlen sich gut an, bis zu deinem ersten Theme. Dann refaktorierst du unter Termindruck – der teuerste Zeitpunkt, es zu tun.
- Kontrast nur im Light Mode getestet. Eine Kombination, die 4.5:1 auf Weiß besteht, kann auf einer dunklen Fläche scheitern und umgekehrt. Jedes semantische Text-auf-Hintergrund-Paar muss in jedem Theme geprüft werden. Die WCAG-Schwellen sind 4.5:1 für normalen Text und 3:1 für großen Text und UI-Komponenten, gemäß der W3C-WCAG-Kontrastanleitung – und dein Token-System ist die Stelle, an der du diese Garantien einbacken solltest, nicht die Stelle, an der du Verstöße entdecken solltest.
- Zu viele Tokens. Ein System mit vierhundert semantischen Tokens ist genauso unbrauchbar wie eines mit vieren. Wenn sich zwei Tokens immer zum selben Wert auflösen und das immer tun werden, sind sie ein Token. Füge Spezifität nur hinzu, wenn eine echte Abweichung auftaucht.
Das ehrliche Fazit ist, dass Tokens dir die Farbentscheidungen nicht abnehmen – sie machen Farbentscheidungen billig änderbar. Die Struktur ist es, die dir die Fähigkeit erkauft, an einem Freitag zu rebranden, den Dark Mode in einem Sprint auszuliefern und einen Kontrast-Bug in einer Zeile statt in vierzig Repositories zu beheben. Investiere deine Mühe in die semantische Schicht und die Benennung, halte Komponenten ehrlich darüber, welche Stufe sie berühren, und das System wird Änderungen absorbieren, die du noch gar nicht vorhersehen kannst.
Häufig gestellte Fragen
Was ist der Unterschied zwischen primitiven und semantischen Farb-Tokens?
Ein primitives (oder Basis-)Token benennt einen rohen Farbwert ohne Kontext – zum Beispiel blue-500: #3B82F6. Es beschreibt, was die Farbe ist. Ein semantisches Token benennt den Zweck der Farbe, etwa color-action-primary oder color-feedback-error, und verweist auf ein Primitiv. Komponenten sollten semantische Tokens referenzieren, niemals Primitive, sodass ein Rebranding oder Theming nur ein Umlenken der semantischen Schicht erfordert, statt jede Komponente zu bearbeiten.
Wie sollte ich Farb-Tokens in einem Designsystem benennen?
Benenne semantische und Komponenten-Tokens nach Absicht, nicht nach Aussehen. color-feedback-error übersteht ein Redesign, bei dem das Fehler-Rot zu Orange wird; color-text-red nicht. Verwende eine Struktur von allgemein zu spezifisch wie category-property-variant-state (z. B. color-bg-surface-raised, color-action-primary-hover). Primitive Tokens sind die Ausnahme – sie sollten die buchstäbliche Farbe beschreiben, wie gray-900, denn das ist ihr ganzer Zweck.
Warum eine numerische Farbskala von 50–950 verwenden?
Die von Tailwind populär gemachte Skala 50–950 (50, 100, 200 … 900, 950) ist zum Industriestandard geworden, sodass sie neuen Entwicklern sofort vertraut ist. 50 ist die hellste Tönung, ~500 ist der reine Markenfarbton, und 950 ist nahezu schwarz. Die Stufen in einem perzeptuellen Modell wie HSL oder OKLCH zu spreizen – statt naiv Weiß und Schwarz in sRGB zu mischen – sorgt dafür, dass sich die Stufen gleichmäßig anfühlen, und vermeidet matschige Mitteltöne.
Wie erleichtern Farb-Tokens den Dark Mode?
Der Dark Mode wird zu einem Token-Mapping-Problem statt zu einem Redesign. Weil Komponenten nur semantische Tokens lesen, überschreibst du diese Tokens innerhalb eines .dark- oder [data-theme=dark]-Bereichs, sodass sie sich zu anderen Primitiven auflösen – zum Beispiel verweist color-bg-surface im Light Mode auf Weiß und im Dark Mode auf gray-900. Die CSS-Kaskade kippt jede Komponente automatisch, ohne Änderungen am Komponentencode. Denke daran, Akzentfarben zu entsättigen und reine schwarze Hintergründe zu vermeiden, um Augenbelastung und Halation zu reduzieren.
Welche Kontrastverhältnisse müssen Farb-Tokens für Barrierefreiheit erfüllen?
WCAG verlangt ein Kontrastverhältnis von mindestens 4.5:1 für normalen Text und 3:1 für großen Text und UI-Komponenten. Backe diese Prüfungen in dein Token-System ein, indem du jede semantische Text-auf-Hintergrund-Kombination in jedem Theme validierst. Eine Kombination, die im Light Mode besteht, kann im Dark Mode scheitern, also teste beide, und definiere ein explizites text-on-action-Token, damit Beschriftungen auf gesättigten Buttons lesbar bleiben.
Lust, mit Farben zu experimentieren?
Probiere unseren kostenlosen Farbpaletten-Generator aus und finde deine perfekte Harmonie — mit integriertem WCAG-Kontrastprüfer.
Generator öffnen