OKLCH-Farbraum erklärt — warum Tailwind v4 ihn übernommen hat
Öffnen Sie den Quellcode eines beliebigen Designsystems aus der 2025er-Ära — shadcn/ui, Radix Themes, die Tailwind-v4-Standardpalette — und das Erste, was ins Auge springt, sind die Farben. Keine HEX-Codes, keine hsl()-Tripletts, sondern eine Funktion, über die vor drei Jahren niemand sprach: oklch(). Tailwind v4 liefert seine gesamte Standardpalette als OKLCH-Literale aus. shadcn generiert inzwischen Themes, die OKLCH-Custom-Properties ausgeben. Das Designsystem von Vercel wurde 2024 darauf neu aufgebaut.
Das ist kein Hype. Es gibt einen konkreten, mathematischen Grund, warum jedes ernstzunehmende Designsystem leise das Farbmodell gewechselt hat, und sobald Sie ihn einmal gesehen haben, können Sie ihn nicht wieder ungesehen machen — Sie verstehen, warum HSL für das, wofür wir es eingesetzt haben, immer schon falsch war.
Dieser Beitrag arbeitet diesen Grund von Grund auf durch, endet mit einer durchgerechneten Konvertierung von einem HEX-Code zu OKLCH und gibt Ihnen ein Migrationsrezept für die eigene Palette in die Hand.
Als die Farbräume versagten
Designsysteme haben eine Tonwert-Aufgabe. Ein Button im Hover-Zustand soll etwas heller wirken als im Ruhezustand. Eine gedämpfte Karte sitzt eine Stufe dunkler als die Oberfläche um sie herum. Ein Fokusring muss sichtbar heller sein als die neutrale Chrome dahinter. Das im großen Maßstab gut zu machen, setzt voraus, dass „heller“ und „dunkler“ über jeden Farbton der Palette dasselbe bedeuten.
Diese Anforderung ließ sich noch ignorieren, als Paletten aus acht Farben und drei Zuständen bestanden. Unbequem wurde sie, als Teams 11-stufige Rampen (50–950 in Tailwind-Konvention), acht semantische Farben, Hell- und Dunkelvarianten sowie Markenakzente auslieferten, die mit Systemfarben aus iOS, Android und dem Web koexistieren mussten. Plötzlich war die Frage „Hat dieses teal-500 dieselbe Helligkeit wie unser blue-500?“ ein echtes Engineering-Problem, kein Art-Direction-Luxus mehr.
HSL — das Arbeitspferd seit CSS 3 — konnte sie nicht beantworten. Zwei HSL-Farben mit identischen L-Werten können sich in der wahrgenommenen Helligkeit dramatisch unterscheiden. Ein reines HSL-Gelb bei lightness: 50% wirkt deutlich heller als ein reines HSL-Blau bei derselben Lightness. Das Auge nimmt Gelb und Blau nicht gleich wahr; HSL war darauf ausgelegt, in Pickern intuitiv zu sein, nicht perzeptuell konsistent für Rampen. Bis 2023 stopfte jedes Designsystem, das über eine Handvoll Farben hinauswuchs, dieses Loch mit eigenen Mixing-Skripten oder von Hand justierten Overrides.
Was wir brauchten, war ein Farbmodell, in dem L tatsächlich „die wahrgenommene Helligkeit, die ein Mensch berichten würde“ bedeutete und in dem das Drehen des Farbtons oder das Reduzieren der Sättigung die Helligkeit nicht als unsichtbaren Nebeneffekt verschob. Dieses Modell existierte in der akademischen Farbwissenschaft — es hatte CSS nur noch nicht erreicht.
Das HSL-Problem, konkret
Geben Sie das in einem Browser aus und schauen Sie sich die drei Felder nebeneinander an:
.a { background: hsl(60 100% 50%); } /* yellow */
.b { background: hsl(240 100% 50%); } /* blue */
.c { background: hsl(120 100% 50%); } /* green */
Alle drei haben L: 50%. Keine von ihnen wirkt, als hätte sie dieselbe Helligkeit. Das Gelb sticht geradezu; das Blau liest sich gegen einen weißen Hintergrund fast schwarz; das Grün liegt irgendwo dazwischen. Bauen Sie einen Hover-Zustand, indem Sie 10% zu L addieren, dann ist der Gelb-Hover kaum sichtbar, während der Blau-Hover ein dramatischer Sprung wird. Ihr Interaktionsfeinschliff hängt am Ende davon ab, mit welchem Farbton die Designerin zufällig angefangen hat.
Das ist kein Bug in HSL. HSL wurde 1978 für Malen-nach-Zahlen-Farbpicker entworfen, in denen Anwenderinnen Hue, Saturation und „Lightness“ — definiert als (max(R,G,B) + min(R,G,B)) / 2 — drehten, um eine Farbe einzustellen. Die Mathematik kennt keine menschliche Wahrnehmung. Lightness in HSL ist der geometrische Mittelpunkt der sRGB-Kanäle, mehr nicht.
Die CIE — der internationale Standardgeber für Farbmetrik — wusste seit den 1970ern um dieses Problem. Sie veröffentlichte zwei perzeptuell uniforme Räume, CIELAB und CIELUV, die Lightness als etwas definierten, das näher an der tatsächlichen Funktionsweise des menschlichen Sehens lag. In den 1990ern war CIE LAB Standard in Druck, Fotografie und Farbmanagement. Aber die Konvertierung nach RGB ist sperrig, und CSS hat ihn nie breit übernommen. Webentwicklerinnen blieben bei HSL, nicht weil es richtig war, sondern weil es da war.
CIE LAB / LCH: der akademische Fix mit eigenen Problemen
CIELAB nimmt einen XYZ-Tristimulus-Wert (ein Modell, wie menschliche Zapfen auf Licht antworten) und führt ihn durch eine Kubikwurzel und eine 2D-Rotation, um drei Kanäle zu erzeugen: L* (Lightness, 0–100), a* (Grün ↔ Rot) und b* (Blau ↔ Gelb). LCH ist derselbe Raum in Polarform: L*, C* (Chroma, Abstand vom Neutralen) und H* (Hue-Winkel).
Diese Räume sind in einem messbaren Sinn perzeptuell uniform. Ein ΔE von 1 — ein Einheitsschritt in irgendeine Richtung im LAB-Raum — entspricht ungefähr dem kleinsten Farbunterschied, den ein geschultes Auge wahrnehmen kann. Druck- und Prepress-Workflows laufen seit Jahrzehnten auf LAB und LCH.
Warum hat CSS dann nicht einfach LCH übernommen und gut?
Zwei Gründe. Erstens wurde CIE LAB gegen eine spezifische Betrachtungsbedingung kalibriert (ein 2°-Standardbeobachter unter D50-Beleuchtung), optimiert für Oberflächenreflexion, nicht für selbstleuchtende Displays. Auf Bildschirmen driftet seine perzeptuelle Uniformität — Farben, die in LAB „gleich hell“ sind, wirken auf einem Telefon nicht immer gleich hell. Zweitens ist der LCH-Farbraum unhandlich. Es gibt sichtbare Farben, die LAB gut beschreibt, die aber außerhalb gängiger Display-Farbräume liegen, und das Abbilden von LCH nach sRGB erzeugt gelegentlich Hue-Shifts (Ihr Blau zieht leicht ins Violette, wenn Sie seine Chroma reduzieren). Für Designsystem-Arbeit sind beides Deal-Breaker.
CSS Color 4 fügte 2021 zwar lab() und lch() hinzu, und sie funktionieren in modernen Browsern. Aber für das spezifische Problem, konsistente Tonwert-Rampen auf selbstleuchtenden Bildschirmen zu bauen, suchte die Community weiter.
OKLAB / OKLCH: Ottossons Einsicht von 2020
Im Dezember 2020 veröffentlichte Björn Ottosson — ein schwedischer Color-Engineer — ein Paper mit dem Titel „A perceptual color space for image processing“. Das Paper war schlank: drei kurze Matrizen, ein Kubikwurzel-Schritt, keine Kalibrierungstabellen, keine urheberrechtlich geschützten Referenzdaten. Ottosson nahm die bestehenden IPT- und CAM16-UCS-Farbmodelle — akademische Räume mit guten Eigenschaften, aber unhandlicher Mathematik — und leitete daraus einen einfacheren Raum ab, der ihr perzeptuelles Verhalten mit gewöhnlichen Matrixmultiplikationen auf linear-licht XYZ-Tristimulus-Werten approximierte.
Er nannte ihn OKLAB. Die Polarform ist OKLCH.
Was OKLCH besonders macht, ist nicht Neuheit, sondern Passgenauigkeit. Drei Eigenschaften zusammen:
- Lightness in OKLCH ist echt perzeptuell. Ein reines Gelb bei
L: 0.7und ein reines Blau beiL: 0.7wirken auf einem kalibrierten Display gleich hell. Hover-Zustände, die alsL + 0.05definiert werden, erzeugen visuell gleichwertige Sprünge über die gesamte Palette. - Der Farbton bleibt unter Chroma-Änderungen erhalten. Reduzieren Sie den C-Wert von
oklch(0.7 0.2 30)aufoklch(0.7 0.1 30), bleibt der Hue stehen. In LCH erzeugt dieselbe Operation häufig einen sichtbaren Hue-Shift. In OKLCH können Sie Chroma flach drücken, um eine gedämpfte Variante einer Markenfarbe zu bauen, ohne dass sie unbeabsichtigt in einen anderen Farbton driftet. - Die Mathematik ist günstig. Zwei Matrixmultiplikationen und eine Kubikwurzel. In einer 30-Zeilen-JavaScript-Funktion implementierbar. Keine Lookup-Tabellen, keine geräteindividuelle Kalibrierung, keine Lizenzfragen.
Diese Kombination machte OKLCH in echtem CSS nutzbar. Das W3C nahm oklch() 2022 in CSS Color 4 auf. Chrome 111 lieferte es 2023 aus. Bis Mitte 2024 unterstützte es jeder Evergreen-Browser. Tailwind v4 machte es im selben Jahr zum Standard-Palettenformat.
Die Mathematik: eine durchgerechnete HEX-→-OKLCH-Konvertierung
Gehen wir #3b82f6 — Tailwinds blue-500 — nach OKLCH durch. Das ist dieselbe Mathematik, die das Farbkonverter-Tool und der HEX-zu-OKLCH-Spoke bei jedem Tastenanschlag ausführen. Zu wissen, was unter der Haube passiert, macht die Fallstricke im nächsten Abschnitt nachvollziehbarer.
Schritt 1: HEX zu sRGB. Den sechsstelligen HEX in drei Paare aufteilen und jedes durch 255 teilen.
const r = 0x3b / 255; // 0.231
const g = 0x82 / 255; // 0.510
const b = 0xf6 / 255; // 0.965
Das sind gamma-encodierte sRGB-Werte: die Kanalwerte, so wie die Bilddatei sie speichert, mit einer nichtlinearen Kurve, die kompensiert, wie Monitore Licht emittieren.
Schritt 2: sRGB nach linearem sRGB. Die Gammakurve entfernen, damit wir linear-licht Kanalwerte haben. Die standardmäßige stückweise Transformation aus CSS Color 4 §11.2:
const linear = (v) => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
const [lr, lg, lb] = [r, g, b].map(linear);
// lr ≈ 0.045, lg ≈ 0.220, lb ≈ 0.923
Schritt 3: Lineares sRGB nach XYZ D65. Eine Standard-Matrixmultiplikation, definiert in CSS Color 4 §15.1:
const x = 0.4124564 * lr + 0.3575761 * lg + 0.1804375 * lb;
const y = 0.2126729 * lr + 0.7151522 * lg + 0.0721750 * lb;
const z = 0.0193339 * lr + 0.1191920 * lg + 0.9503041 * lb;
// x ≈ 0.265, y ≈ 0.231, z ≈ 0.927
Das ist die kanonische XYZ-Tristimulus-Repräsentation — die Form „welche Wellenlängen ergibt diese Farbe, ausgedrückt in der Antwort der menschlichen Zapfen“.
Schritt 4: XYZ nach LMS. Ottossons erste Matrix bildet XYZ auf einen Long/Medium/Short-Zapfen-Fundamentalraum ab, der für OKLAB abgestimmt ist:
const lms = [
0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z,
0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z,
0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z,
];
Schritt 5: Kubikwurzel der LMS-Werte. Das ist der perzeptuelle Kompressionsschritt — analog zur Kubikwurzel in CIE LAB:
const lms_ = lms.map(Math.cbrt);
Schritt 6: LMS’ nach OKLAB. Ottossons zweite Matrix:
const L = 0.2104542553 * lms_[0] + 0.7936177850 * lms_[1] - 0.0040720468 * lms_[2];
const a = 1.9779984951 * lms_[0] - 2.4285922050 * lms_[1] + 0.4505937099 * lms_[2];
const b_ = 0.0259040371 * lms_[0] + 0.7827717662 * lms_[1] - 0.8086757660 * lms_[2];
// L ≈ 0.629, a ≈ -0.022, b_ ≈ -0.191
Das ist OKLAB. Der Lightness-Kanal L ≈ 0.629 ist das, was das Auge bei diesem konkreten Blau als „60 % Helligkeit“ wahrnimmt.
Schritt 7: OKLAB nach OKLCH (kartesisch zu polar).
const C = Math.sqrt(a * a + b_ * b_); // 0.193
const H = (Math.atan2(b_, a) * 180 / Math.PI + 360) % 360; // 263.4
// → oklch(0.629 0.193 263.4)
Also ist #3b82f6 gleich oklch(0.629 0.193 263.4). Lightness 0,629, Chroma 0,193, Hue 263,4°.
Bauen Sie aus dieser Farbe eine 50–950-Rampe, indem Sie ausschließlich L variieren (von 0,95 in 11 Schritten auf 0,15), während Sie C und H fest halten, erhalten Sie eine Palette, in der jede Stufe sichtbar denselben Farbton hat und gleichmäßig in der Lightness verblasst. Tun Sie dasselbe in HSL, ziehen die dunklen Stufen ins Violette, während die hellen Stufen vergrauen. Das ist der Gewinn.
Display P3 + Rec2020: warum OKLCH den weiten Farbraum aufschließt
OKLCH ist unbeschränkt. Anders als HSL (auf sRGB gekappt) und sogar LCH (auf reflektierende Oberflächen kalibriert) hat OKLCH keinen impliziten Farbraum. Sie können oklch(0.7 0.25 30) schreiben und erhalten ein leuchtendes Rot, das innerhalb des Farbvolumens von Display P3 liegt, aber außerhalb von sRGB. Auf einem aktuellen iPhone oder MacBook wird es gerendert. Auf einem älteren Monitor schnappt der Browser es automatisch auf die nächste sRGB-Repräsentation.
Das ist relevant, weil Apple, Samsung und das W3C in den späten 2010er-Jahren breitflächig Wide-Gamut-Hardware ausgeliefert haben. Das MacBook Pro 14” / 16” mit Mini-LED kommt standardmäßig mit P3. Das iPhone 15 Pro rendert Display P3 in Safari. Android-Flagships kommen mit Rec2020-Panels. Bis 2025 liegt ein erheblicher Anteil des Designsystem-Traffics auf Wide-Gamut-Hardware, die Farben anzeigen kann, die HSL/sRGB schlicht nicht ausdrücken können.
OKLCH lässt Sie diese Farben schreiben, ohne eine separate @media (color-gamut: p3)-Deklaration aufzupfropfen. Der Browser kümmert sich um den Fallback. Ihr Designsystem bekommt von Haus aus ein „nimm das hellste Rot, das das Gerät rendern kann“.
Das ist auch der Grund, warum OKLCH das richtige Format für Design-Tokens ist. Eine --brand-Variable in OKLCH ist eine gerätenunabhängige Beschreibung der Absicht. Der Browser entscheidet, was er auf dem jeweiligen Display der Nutzerin rendert, und Ihr Code ist portierbar über CSS, SwiftUI (das Display P3 nativ unterstützt), Android Compose (Rec2020-bewusst) und Flutter.
Tailwind v4 und die Design-Token-Revolution
Tailwind v4 — 2024 veröffentlicht — war der Wendepunkt, der OKLCH von Forschung zum Industriestandard machte. Die Tailwind-Autoren trafen drei dezidierte Entscheidungen:
- Die Standardpalette ist OKLCH. Slate, gray, zinc, neutral, stone — jede Tailwind-Farbe ist im Quellcode in
oklch()definiert. Die 50–950-Rampen sind konstruktionsbedingt uniform in der perzeptuellen Lightness. - Custom Themes nutzen
@theme-Blöcke mit OKLCH-Literalen. Markenfarben werden alsoklch()-Tokens definiert; nachgelagerte Utilities (bg-brand-500,text-brand-300) werden generiert. - Kein Fallback-Zeremoniell nötig. Browser ohne OKLCH-Unterstützung liegen unter dem dokumentierten Baseline von Tailwind v4.
Diese letzte Entscheidung machte die Adoption zur Fußschuss-freien Wahl. Noch 2023 mussten Designer sowohl oklch()- als auch hsl()-Versionen jeder Farbe ausliefern, damit ältere Safari-Versionen nicht brachen. Mit Tailwind v4 ist die Baseline „Browser ab 2023 oder neuer“, und OKLCH funktioniert überall.
Der Theme-Generator von shadcn/ui folgt demselben Muster: Markenfarbe eingeben, OKLCH-Rampe herausbekommen. Vercels Designsystem nutzt OKLCH für seine semantischen Farben. Die Color-Scales von Radix Themes sind in OKLCH definiert. Die Community ist sich einig.
Praktische Migration: HEX-Palette → OKLCH-Palette
Wenn Sie heute eine HEX-basierte Palette haben, ist die Migration mechanisch. Hier das Rezept:
1. Legen Sie die Rampenstruktur fest. Tailwinds 50–950 (11 Stops) ist der De-facto-Standard und es lohnt sich, ihn zu übernehmen, sofern Sie keinen konkreten Grund dagegen haben. Stops bei L = 0,97, 0,93, 0,86, 0,76, 0,63, 0,50, 0,42, 0,34, 0,26, 0,18, 0,10 ergeben eine glatte perzeptuelle Rampe.
2. Konvertieren Sie Ihren Marken-HEX zu OKLCH. Nutzen Sie den Farbkonverter oder das Tool HEX zu OKLCH. Sie erhalten ein Tripel wie oklch(0.629 0.193 263.4). Merken Sie sich den H-Wert — das ist Ihr Marken-Hue.
3. C und H konstant halten; nur L variieren. Bauen Sie die Rampe, indem Sie ausgeben:
--brand-50: oklch(0.97 0.193 263.4);
--brand-100: oklch(0.93 0.193 263.4);
...
--brand-950: oklch(0.10 0.193 263.4);
4. Extremstops feinjustieren. Bei sehr niedrigem L (≤ 0,20) und sehr hohem L (≥ 0,95) fallen hohe Chroma-Werte außerhalb von sRGB. Reduzieren Sie C für diese Stops oder akzeptieren Sie das automatische Snap des Browsers. Tailwinds Defaults reduzieren die Chroma an beiden Enden — kopieren Sie dieses Muster.
5. Semantische Aliase definieren. --surface: var(--brand-50), --surface-elevated: var(--brand-100), --text-primary: var(--brand-900) usw. Jetzt lesen sich Ihre Design-Tokens als Absicht, nicht als Farben.
6. Kontrast prüfen. Nutzen Sie APCA Lc oder WCAG-2-Kontrastverhältnisse, um sicherzustellen, dass jedes --text-*-Paar gegen jedes --surface-*-Paar Ihre Accessibility-Schwelle erfüllt. Da OKLCHs L perzeptuell ist, ist die Kontrast-Mathematik verlässlicher als in HSL.
Ein Team, das diese Migration auf einer 60-farbigen Legacy-Palette durchführt, landet typischerweise an einem Nachmittag bei einer kleineren, uniformeren OKLCH-Palette mit 30–40 Tokens. Die neue Palette wird kleiner ausgeliefert, erzeugt kleineres CSS und produziert ohne weiteres Feintuning sichtbar bessere Tonwertbewegungen in Hover- und Disabled-Zuständen.
Fallstricke und wie Sie sie handhaben
Ein paar Dinge zum Mitnehmen:
Out-of-Gamut-Warnungen. Manche OKLCH-Werte liegen außerhalb von Display P3 oder sRGB. Moderne Browser kümmern sich automatisch um das Snap auf die nächstgelegene gültige Farbe, aber dieses Snap ist verlustbehaftet: Ihr oklch(0.7 0.25 30) rendert möglicherweise etwas weniger gesättigt als geschrieben. Werkzeuge wie die Gamut-Zeile des Farbkonverters sagen Ihnen, ob Ihre Farbe sRGB-, P3- oder Rec2020-sicher ist, und bieten ein Ein-Klick-Snap-to-sRGB, damit das Geschriebene auch das Gesehene ist.
Sub-Pixel-Chroma-Eigenheiten. OKLCH-Chroma ist unbeschränkt, der nützliche Bereich liegt für sichtbare Farben aber grob zwischen 0 und etwa 0,4. Werte über 0,4 sind nur mit monochromatischem Laserlicht erreichbar — sie sind keine physischen Farben, die ein Display rendern könnte. Der Farbkonverter cappt den Chroma-Schieber aus diesem Grund bei 0,4; Werte darüber erzeugen auf keinem realen Display einen wahrnehmbaren Unterschied.
Browser-Unterstützung, Stand 2025. Chrome 111+, Safari 15.4+ und Firefox 113+ unterstützen oklch() nativ. Browser vor 2023 tun das nicht. Müssen Sie Legacy-IE/Edge oder älteres Mobile-Safari (je nach Zielgruppe 1–3 % des Traffics) bedienen, können Sie eine OKLCH-Deklaration mit einem HEX-Fallback über @supports (color: oklch(0 0 0)) koppeln — für Designsystem-Tokens, die 2025 ausgeliefert werden, überwiegen aber meist die Kosten des Fallbacks den Legacy-Nutzen.
HEX-Code-Permanenz. OKLCH dient der Designsystem-Absicht. Ihr CMS braucht aus Legacy-Gründen vielleicht noch einen HEX-Wert (E-Mail-Signaturen, Office-Dokumente, Markenasset-Checklisten). Halten Sie eine generierte Lookup-Tabelle vor, die für jedes OKLCH-Token den sRGB-geschnappten HEX ausgibt, aber autorisieren Sie nicht in HEX.
OKLCH und OKLAB nicht vermischen. OKLAB ist die rechteckige Form (L-, a-, b-Kanäle); OKLCH ist derselbe Farbraum in Polarform (L, C, H). Sie konvertieren zwischen beiden mit einem kartesisch-polaren Schritt. Verwenden Sie OKLCH für Tokens (besser lesbar, einfacher zu rampen); verwenden Sie OKLAB intern, wenn Sie Farben interpolieren oder mischen müssen.
Probieren Sie es an Ihrer eigenen Palette
Der schnellste Weg, das hier Beschriebene in Aktion zu sehen, ist, einen Marken-HEX in den Farbkonverter zu werfen. Tippen Sie Ihre Markenfarbe in das HEX-Feld und lesen Sie die OKLCH-Ausgabe. Bewegen Sie dann die Schieber auf der OKLCH-Seite und beobachten Sie, wie derselbe Farbton derselbe Farbton bleibt, während Sie die Chroma reduzieren, und wie dieselbe Lightness dieselbe Lightness bleibt, während Sie den Hue rotieren. Nach ein paar Minuten haben Sie ein intuitives Gefühl dafür, warum HSL für Tonwert-Rampen immer das falsche Werkzeug war und warum jedes ernstzunehmende Designsystem weitergezogen ist.
Für die spezifische HEX-zu-OKLCH-Konvertierung nutzen Sie HEX zu OKLCH — dieselbe Mathematik wie in diesem Artikel, mit einem zusätzlichen Vorteil: Es zeigt die Gamut-Klassifizierung (sRGB / Display P3 / Rec2020), sodass Sie wissen, welche Ihrer Markenfarben überall sicher sind und welche ein Wide-Gamut-Gerät brauchen, um voll dargestellt zu werden.
Das ist OKLCH. Die Migration ist es wert. Sauber gemacht, schreiben Sie nie wieder hsl().