UI-Design

Skalierbare Farb-Tokens für Designsysteme erstellen

Vom colorPaletteFinder-Team8 Min. Lesezeit

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.

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:

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:

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:

Die Stolperfallen, die im Maßstab tatsächlich zerbrechen

Nach genügend Rebrandings und Theme-Launches reimen sich die Fehlerbilder:

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