Eine Farbskala entsteht in den meisten Projekten so: Man nimmt die Markenfarbe, zieht im Farbwähler ein paar hellere und ein paar dunklere Varianten daraus und schreibt zehn Hex-Werte in die Tokens. Das Ergebnis sieht beim ersten Blick in Ordnung aus und fällt im dritten Monat auseinander. Eine Stufe wirkt schmutzig, zwei Stufen in der Mitte sind kaum zu unterscheiden, und derselbe Hover-Effekt, der auf dem Blau gut aussieht, verschwindet auf dem Gelb. Der Grund ist nicht mangelndes Gespür, sondern das Format: Hex und HSL beschreiben, wie ein Bildschirm eine Farbe erzeugt, nicht wie ein Mensch sie wahrnimmt. OKLCH beschreibt das zweite. Dieser Beitrag zeigt, was die drei Achsen bedeuten, wie daraus eine Skala entsteht, die man nicht mehr nachkorrigieren muss, und wo color-mix die Arbeit übernimmt, für die man sonst einen weiteren Token angelegt hätte.
- Das L in OKLCH ist wahrgenommene Helligkeit. Zwei Farben mit demselben L wirken gleich hell, egal welcher Farbton. Das L in HSL kann das nicht leisten: Gelb und Blau stehen dort beide bei 50 Prozent und sehen völlig verschieden aus.
- Eine Skala ist eine Liste von Helligkeiten. Farbton bleibt fest, die Helligkeit läuft in gleichen Schritten durch, und das Chroma folgt einem Bogen mit dem höchsten Wert in der Mitte. Ein konstantes Chroma macht die hellen Stufen grau und die dunklen flach.
- color-mix und oklch sind seit 2023 in allen großen Browsern verfügbar und brauchen keine Rückfallebene. Gemischt wird in oklab, sonst läuft der Weg zwischen zwei Farben durch eine stumpfe Mitte.
- Gleicher Helligkeitsabstand ist nicht gleicher Kontrast. Mit L wählt man eine Stufe aus, über 4,5 zu 1 entscheidet weiterhin ein Kontrastwert, und der rechnet in sRGB.
Warum verrät ein Hex-Wert nichts über seine Helligkeit?
Ein Hex-Wert ist eine Anweisung an den Bildschirm. Drei Zahlen sagen, wie stark die rote, die grüne und die blaue Leuchtquelle arbeiten sollen. Darin steckt keine Angabe darüber, wie hell das Ergebnis aussieht, und man merkt das sofort am Vergleich: Gelb mit ffff00 und Blau mit 0000ff sind beide auf zwei Kanälen voll aufgedreht. Das Gelb ist blendend hell, das Blau beinahe dunkel. Dasselbe Format, derselbe Aufwand für den Bildschirm, zwei völlig verschiedene Eindrücke.
HSL verspricht Besserung und hält das Versprechen nur halb. Das L dort heißt Lightness und ist doch keine wahrgenommene Helligkeit, sondern eine geometrische Lage in einem Zylinder. hsl(60 100% 50%) ist jenes grelle Gelb, hsl(240 100% 50%) jenes dunkle Blau, und beide stehen bei 50 Prozent. Wer eine Skala baut, indem er in HSL gleichmäßige Schritte auf dem L nimmt, bekommt deshalb ungleichmäßige Sprünge: In der Mitte liegen zwei Stufen zu nah beieinander, am hellen Ende kippt die Farbe ins Graue, am dunklen verliert sie ihren Charakter.
OKLCH wechselt den Bezugspunkt. Es beschreibt eine Farbe nicht über die Arbeit des Bildschirms, sondern über ein Modell der Wahrnehmung. Die erste Achse ist der Helligkeitseindruck, und sie gilt quer über alle Farbtöne: Zwei Werte mit demselben L wirken gleich hell, ob gelb, blau oder rot. Damit ist Helligkeit eine Größe, mit der man rechnen kann, und genau das braucht eine Skala.
Hex speichert Werte. OKLCH beschreibt Eindrücke. Nur mit dem zweiten lässt sich rechnen.
Was beschreiben L, C und H?
L ist die Helligkeit und läuft von 0 bis 1, in Prozent geschrieben von 0 bis 100. Null ist Schwarz, Eins ist Weiß, und die Werte dazwischen sind so verteilt, dass gleiche Abstände gleich aussehen. Ein Sprung von 0,40 auf 0,50 wirkt so groß wie der von 0,70 auf 0,80. Das ist die eine Eigenschaft, um die es bei diesem Format geht.
C ist das Chroma, also der Abstand zu Grau. Bei 0 bleibt nur ein Grauton übrig, und zwar unabhängig vom Farbton. Nach oben gibt es keine feste Grenze: Chroma ist kein Anteil, der bei 100 Prozent endet, sondern eine offene Strecke. In der Praxis liegt die obere Kante bei etwa 0,4, und wenn man die Prozentschreibweise benutzt, entspricht 100 Prozent genau diesem Wert. Wie weit man tatsächlich kommt, hängt von L und H ab und vom Bildschirm.
H ist der Farbton in Grad. Hier liegt die Stolperstelle beim Umstieg: Der Kreis ist anders gedreht als in HSL. Magenta beginnt bei 0, Rot liegt bei etwa 41 Grad, und ein Gradwert aus einer HSL-Angabe lässt sich deshalb nicht übernehmen. Wer eine bestehende Farbe umstellt, rechnet sie um statt sie abzuschreiben, und zwar einmal, danach steht der Wert.
--gelb: oklch(0.72 0.15 95); --blau: oklch(0.72 0.15 255); --rot: oklch(0.72 0.15 25); /* Prozentschreibweise, derselbe Wert */--gelb-prozent: oklch(72% 37.5% 95); /* Chroma auf null: drei identische Grautöne */--grau: oklch(0.72 0 95);
Wie wird aus einer Farbe eine Skala?
Der Ausgangspunkt ist die eine Farbe, die bereits feststeht, also die Markenfarbe. Rechne sie nach OKLCH um und lies die drei Werte ab. Der Farbton daraus ist von nun an gesetzt und wandert durch alle Stufen unverändert mit. Damit ist die Frage, was eine Skala eigentlich ist, schon beantwortet: eine Liste von Helligkeiten zu einem festen Farbton.
Die Helligkeiten verteilt man in gleichen Abständen, und weil das L wahrgenommene Helligkeit beschreibt, sehen die Abstände danach auch gleich aus. Zehn Stufen von 0,97 bis 0,22 sind ein bewährter Rahmen: oben die Flächen, die kaum getönt sind, in der Mitte die eigentliche Markenfarbe, unten das, was Text auf heller Fläche tragen muss. Die Basis gehört in die Mitte, nicht an den Rand, denn sonst fehlt auf einer Seite der Platz für Zustände.
Das Chroma bleibt dabei nicht konstant, und das ist der Teil, den man beim ersten Versuch meist übersieht. Nahe Weiß und nahe Schwarz gibt es kaum Raum für Buntheit, in der Mitte am meisten. Ein konstanter Wert über alle Stufen macht die hellen Töne schmutzig und die dunklen flach. Das Chroma folgt deshalb einem Bogen: klein oben, am höchsten um die Basis, wieder kleiner nach unten. Eine leichte Drehung des Farbtons über die Skala hinweg ist möglich und manchmal gewollt, aber sie ist eine Entscheidung, keine Notwendigkeit. Fang ohne sie an.
@theme { --color-brand-50: oklch(0.97 0.013 62); --color-brand-100: oklch(0.93 0.032 62); --color-brand-200: oklch(0.88 0.062 62); --color-brand-300: oklch(0.81 0.095 62); --color-brand-400: oklch(0.74 0.125 62); --color-brand-500: oklch(0.67 0.148 62); --color-brand-600: oklch(0.58 0.13 62); --color-brand-700: oklch(0.49 0.112 62); --color-brand-800: oklch(0.38 0.082 62); --color-brand-900: oklch(0.28 0.055 62);}
- Gleiche Schritte auf dem L in HSL nehmen. Die Sprünge wirken ungleich, weil dieses L keine Helligkeit beschreibt, sondern eine Lage im Zylinder.
- Das Chroma über alle Stufen gleich lassen. Oben wird der Ton grau und schmutzig, unten verliert er seinen Charakter.
- Die Markenfarbe als hellste oder dunkelste Stufe setzen. Dann fehlt auf einer Seite der Platz für Hover, Fokus und Aktiv.
- Jede Stufe einzeln im Farbwähler suchen, bis sie gefällt. Das Ergebnis ist bei zehn Stufen nie konsistent und bei zwei Rampen nie vergleichbar.
- Den Farbton einmal festlegen und über alle Stufen unverändert lassen. Damit gehören die zehn Werte sichtbar zusammen.
- Die Helligkeit in gleichen Abständen verteilen, etwa von 0,97 bis 0,28. Gleiche Abstände wirken dann auch gleich.
- Das Chroma einem Bogen folgen lassen, mit dem höchsten Wert um die Basis herum.
- Die Basis in die Mitte legen, damit nach oben und nach unten je vier Stufen für Zustände und Flächen übrig bleiben.
Eine Skala ist eine Liste von Helligkeiten. Farbton und Chroma folgen daraus.
Wenige Rollen schlagen viele Werte.
Eine Liste aus Hex-Zahlen ist eine Bestandsaufnahme. Ein System wird daraus erst, wenn jeder Wert eine Aufgabe hat.
Wofür reicht color-mix?
Nicht jede Farbe im Projekt muss in der Skala stehen. Ein Rand, der eine Spur heller als die Fläche ist, ein Hover, der eine Spur dunkler sitzt, eine Hinterlegung für eine Auswahl: Das sind Ableitungen, und für Ableitungen gibt es color-mix. Die Funktion nimmt zwei Farben, einen Prozentwert und den Raum, in dem gemischt wird. Ein Prozentwert reicht, den Rest nimmt der Browser für die zweite Farbe.
Der Raum ist dabei keine Nebensache. In srgb läuft der Weg zwischen zwei kräftigen Farben durch eine stumpfe Mitte, weil dort die Kanäle gegeneinander arbeiten. In oklab bleibt die Mischung über den ganzen Weg lebendig, und die Helligkeit verhält sich so, wie man es von einem Mischungsverhältnis erwartet. Schreib deshalb in oklab, wenn du nicht bewusst etwas anderes willst.
Für Zustände gibt es noch einen zweiten Weg, und der ist präziser: die relative Farbschreibweise. oklch(from var(--color-brand-500) calc(l - 0.08) c h) nimmt die Basis auseinander, senkt nur die Helligkeit und lässt Chroma und Farbton unberührt. Zu beachten ist, dass die Kanäle dabei als Zahlen vorliegen: calc(l - 0.08) ist gültig, calc(l - 8%) nicht. Diese Schreibweise ist jünger als color-mix, verfügbar ab Chrome und Edge 119, Firefox 128 und Safari 16.4, wobei die frühen Safari-Fassungen einem älteren Entwurf folgten. Ein @supports-Block fragt sie sauber ab.
/* Fläche aufhellen, gegen die eigene Oberfläche */ --surface-raised: color-mix(in oklab, var(--color-surface) 92%, var(--color-brand-500)); /* Rand aus der Textfarbe, ohne eigenen Wert */ --border-soft: color-mix(in oklab, currentColor 14%, transparent); /* Hover: nur die Helligkeit eine Stufe tiefer */@supports (color: oklch(from white l c h)) { .knopf:hover { background: oklch(from var(--color-brand-500) calc(l - 0.08) c h); }}
Welche Farben gehören in die Tokens?
Zehn Stufen pro Rampe sind eine Palette, kein System. Wer im Markup bg-brand-600 schreibt, hat eine Zahl ins Layout geschrieben und muss sie beim nächsten Themewechsel überall wieder suchen. Zwischen Rampe und Markup gehört deshalb eine Ebene aus Rollen, und sie ist kurz: Was ist Fläche, was ist Text, was ist Rand, was ist Akzent.
Eine Rolle zeigt auf eine Stufe. Beim Wechsel des Themes ändert sich nur, auf welche Stufe sie zeigt, die Rampe selbst bleibt unverändert. Das ist der Unterschied zwischen einem Satz Farben, den man pflegen kann, und einem, den man jedes halbe Jahr neu sortiert.
- Die Rampe bleibt neutral benannt. brand-50 bis brand-900 beschreiben Helligkeiten, nicht Verwendungen. Sie stehen einmal im @theme-Block und werden danach nicht mehr angefasst, auch nicht beim Themewechsel.
- Die Rolle sagt, wofür. surface, surface-muted, text, text-muted, border, accent: vier bis sechs Namen tragen überraschend weit. Jeder zeigt auf genau eine Stufe der Rampe oder auf eine neutrale Graustufe.
- Zustände werden abgeleitet, nicht aufgelistet. Hover, Fokus und Aktiv sind keine eigenen Tokens, sondern eine Rechnung auf dem Akzent. Mit color-mix oder der relativen Schreibweise entstehen sie an der Stelle, an der sie gebraucht werden.
- Ausnahmen bekommen einen eigenen Namen. Die eine Farbe, die aus dem System fällt, etwa ein Signalrot für eine Warnung, gehört nicht in die Rampe gezwungen. Ein eigener Token mit sprechendem Namen ist ehrlicher als eine Stufe, die nirgends dazupasst.
In Tailwind ab Version 4 fällt der zweite Teil praktisch zusammen: Ein Eintrag --color-surface in @theme erzeugt die Utility bg-surface und hinterlässt dieselbe Variable als native Custom Property. Damit lesen sich Markup und Stylesheet gleich, und ein Blick ins Markup verrät die Absicht statt einer Zahl.
- primaryPrimärfarbe#1f5eff
- surfaceFläche#f8fafc
- textText#0f172a
- borderRand#e2e8f0
Links viele Werte, die sich kaum unterscheiden. Rechts die Rollen, die davon übrig bleiben.
Was prüft man, bevor es live geht?
Eine durchgerechnete Skala ist gleichmäßig, und gleichmäßig ist nicht dasselbe wie geprüft. Drei Dinge lohnen einen eigenen Durchgang, und keines davon meldet sich von selbst.
- Kontrast rechnet woanders. Der Wert aus den WCAG beruht auf relativer Luminanz in sRGB, nicht auf dem L aus OKLCH. Gleiche L-Abstände können deshalb auf verschiedenen Seiten der Linie von 4,5 zu 1 landen. Wähle die Stufe mit L und prüfe das Paar danach mit einem Kontrastwerkzeug.
- Zu kräftig gibt es. Liegt ein Chroma außerhalb des darstellbaren Bereichs, sucht der Browser die nächstgelegene Farbe: Helligkeit und Farbton bleiben, das Chroma sinkt. Auf einem Bildschirm mit größerem Farbraum fällt das kleiner aus. Verlass dich deshalb nie auf 0,02 Chroma, um zwei Zustände zu unterscheiden.
- Das dunkle Theme braucht weniger Buntheit. Derselbe Farbton auf dunkler Fläche wirkt kräftiger, oft bis zum Glühen. Spiegle die Helligkeit, behalte den Farbton und nimm das Chroma etwas zurück. Eine Rampe, die hell gut funktioniert, ist dunkel selten unverändert richtig.
Was keine Prüfung braucht, ist die Verfügbarkeit der beiden wichtigsten Teile: oklch und color-mix sind seit 2023 in allen großen Browsern vorhanden und gelten als etabliert. Eine Rückfallebene in Hex ist dort reine Pflege ohne Gegenwert. Nur die relative Schreibweise mit from ist jünger, und sie ist der eine Teil, den ein @supports-Block umschließen darf, wenn du noch ältere Fassungen von Safari bedienst.
Bleibt am Ende die unbequemste Frage, und sie hat nichts mit dem Format zu tun: Braucht das Projekt wirklich zehn Stufen pro Rampe? In den meisten Oberflächen sind vier davon nie im Einsatz. Eine Rampe aus sechs Stufen, die alle gebraucht werden, ist leichter zu pflegen als eine aus zehn, bei der niemand mehr weiß, wofür die 200 gedacht war.
Mit L wählt man aus. Über bestanden oder nicht entscheidet ein Kontrastwert.




