OKLCH Design Tokens

Skalierbares Farbsystem

Als ich das Farbsystem für dieses Projekt definiert habe, wollte ich etwas entwickeln, das flexibel und zugleich zukunftssicher ist. Klassische Hex- oder RGB-Werte funktionieren für statische Paletten gut, stoßen aber schnell an Grenzen, wenn Helligkeit, Kontrast oder Barrierefreiheit fein abgestimmt werden sollen. Deshalb habe ich die Palette auf Basis des OKLCH-Farbmodells aufgebaut. Im Unterschied zu HSL oder HEX ist OKLCH wahrnehmungsorientiert, Veränderungen bei Helligkeit und Chroma verhalten sich also deutlich näher an dem, wie wir Farbe tatsächlich sehen. Dadurch lassen sich Abstufungen, Kontraste und Harmonien wesentlich präziser steuern.

OKLCH / 04 × 11

Vier Farbtöne. Ein System.

Von hell nach dunkel: Jede Reihe behält Chroma und Farbton bei. Nur die Helligkeit (L) verändert sich. Die markierte Stufe 50 ist die Basisfarbe.

Primär C 0.15 · H 272.0°
  1. 0 L 90%
  2. 10 L 80%
  3. 20 L 70%
  4. 30 L 60%
  5. 40 L 50%
  6. 50Basis L 40%
  7. 60 L 30%
  8. 70 L 20%
  9. 80 L 10%
  10. 90 L 5%
  11. 100 L 0%
Sekundär C 0.13 · H 10.92°
  1. 0 L 95%
  2. 10 L 85%
  3. 20 L 73%
  4. 30 L 55%
  5. 40 L 40%
  6. 50Basis L 30%
  7. 60 L 25%
  8. 70 L 20%
  9. 80 L 17%
  10. 90 L 12%
  11. 100 L 8%
Teal C 0.1099 · H 198.0°
  1. 0 L 99%
  2. 10 L 90%
  3. 20 L 85%
  4. 30 L 75%
  5. 40 L 70%
  6. 50Basis L 65%
  7. 60 L 58%
  8. 70 L 45%
  9. 80 L 32%
  10. 90 L 24%
  11. 100 L 18%
Signal C 0.1659 · H 119.42°
  1. 0 L 99%
  2. 10 L 93%
  3. 20 L 91%
  4. 30 L 87%
  5. 40 L 83%
  6. 50Basis L 80%
  7. 60 L 76%
  8. 70 L 60%
  9. 80 L 50%
  10. 90 L 40%
  11. 100 L 20%

0–100 bezeichnet die Token-Stufen, nicht den L-Wert. Farben außerhalb des darstellbaren Farbraums werden vom Browser angepasst; Kontraste müssen für die jeweilige Kombination geprüft werden.

/* Primary Palette */
--primary-color: oklch(40% 0.15 272);
--color-primary-0: oklch(from var(--primary-color) 90% c h);
--color-primary-10: oklch(from var(--primary-color) 80% c h);
--color-primary-20: oklch(from var(--primary-color) 70% c h);
--color-primary-30: oklch(from var(--primary-color) 60% c h);
--color-primary-40: oklch(from var(--primary-color) 50% c h);
--color-primary-50: var(--primary-color);
--color-primary-60: oklch(from var(--primary-color) 30% c h);
--color-primary-70: oklch(from var(--primary-color) 20% c h);
--color-primary-80: oklch(from var(--primary-color) 10% c h);
--color-primary-90: oklch(from var(--primary-color) 5% c h);
--color-primary-100: oklch(from var(--primary-color) 0% c h);

Die Grundlage der Palette bildet eine Primärfarbskala, bei der ich den Helligkeitswert von 90 % bis auf 0 % variiert habe, während Chroma und Hue konstant bleiben. So entstand aus einem Ausgangston ein gleichmäßiges Spektrum aus Tints und Shades. Darauf aufbauend kamen eine Sekundärfarbe, eine Alert-Farbe für Zustände und Hinweise sowie ein spielerischerer Akzentton in Teal hinzu. Alle Skalen folgen demselben Prinzip: von einer Basisfarbe ausgehend Abstufungen über die Helligkeit in OKLCH aufbauen.

Damit die Palette auch in der Entwicklung praktisch nutzbar ist, habe ich jede Stufe in CSS Custom Properties übersetzt. Jede Farbe erhält einen Variablennamen wie --color-primary-30 oder --color-teal-70, der ihre Position in der Skala bezeichnet, nicht ihren Helligkeitswert in Prozent. Ergänzend dazu habe ich Utility-Klassen wie .bg-primary-30 oder .text-teal-70 angelegt, damit sich die Farben direkt im Markup verwenden lassen. So bleiben die Design-Tokens konsistent, Farben lassen sich später leichter austauschen oder erweitern und die Arbeit mit der Palette in realen Layouts wird deutlich klarer.

/* Secondary Palette */
--secondary-color: oklch(30% 0.13 10.92);
--color-secondary-0: oklch(from var(--secondary-color) 95% c h);
--color-secondary-10: oklch(from var(--secondary-color) 85% c h);
--color-secondary-20: oklch(from var(--secondary-color) 73% c h);
--color-secondary-30: oklch(from var(--secondary-color) 55% c h);
--color-secondary-40: oklch(from var(--secondary-color) 40% c h);
--color-secondary-50: var(--secondary-color);
--color-secondary-60: oklch(from var(--secondary-color) 25% c h);
--color-secondary-70: oklch(from var(--secondary-color) 20% c h);
--color-secondary-80: oklch(from var(--secondary-color) 17% c h);
--color-secondary-90: oklch(from var(--secondary-color) 12% c h);
--color-secondary-100: oklch(from var(--secondary-color) 8% c h);
/* Teal Palette */
--teal-color: oklch(65% 0.1099 198);
--color-teal-0: oklch(from var(--teal-color) 99% c h);
--color-teal-10: oklch(from var(--teal-color) 90% c h);
--color-teal-20: oklch(from var(--teal-color) 85% c h);
--color-teal-30: oklch(from var(--teal-color) 75% c h);
--color-teal-40: oklch(from var(--teal-color) 70% c h);
--color-teal-50: var(--teal-color);
--color-teal-60: oklch(from var(--teal-color) 58% c h);
--color-teal-70: oklch(from var(--teal-color) 45% c h);
--color-teal-80: oklch(from var(--teal-color) 32% c h);
--color-teal-90: oklch(from var(--teal-color) 24% c h);
--color-teal-100: oklch(from var(--teal-color) 18% c h);
/* alert Palette */
--alert-color: oklch(80% 0.1659 119.42);
--color-alert-0: oklch(from var(--alert-color) 99% c h);
--color-alert-10: oklch(from var(--alert-color) 93% c h);
--color-alert-20: oklch(from var(--alert-color) 91% c h);
--color-alert-30: oklch(from var(--alert-color) 87% c h);
--color-alert-40: oklch(from var(--alert-color) 83% c h);
--color-alert-50: var(--alert-color);
--color-alert-60: oklch(from var(--alert-color) 76% c h);
--color-alert-70: oklch(from var(--alert-color) 60% c h);
--color-alert-80: oklch(from var(--alert-color) 50% c h);
--color-alert-90: oklch(from var(--alert-color) 40% c h);
--color-alert-100: oklch(from var(--alert-color) 20% c h);