BlogWebentwicklung

Chart-Farben, die jeder Markenfarbe folgen – mit OKLCH

Alle Chart-Farben aus der Akzentfarbe des Kunden ableiten, mit CSS Relative Colors in OKLCH: eine Zeile pro Farbton, lesbare Labels, eigener Dark Mode.

von Karin Huber11 Min. Lesezeit

Wenn in Ihrem Produkt jeder Kunde eine eigene Akzentfarbe setzen kann, haben Ihre Charts ein Problem. Eine feste Kategorien-Palette passt zur eigenen Marke und beißt sich mit der des nächsten Kunden. Früher oder später liegt eine Palettenfarbe direkt neben der Akzentfarbe und hat fast denselben Farbton, oder ein gelber Balken trägt ein weißes Label, das niemand lesen kann.

Genau darauf sind wir in time cockpit gestoßen, wo jeder Tenant seine eigene Akzentfarbe mitbringt. Die Lösung war am Ende klein: Jede Chart-Farbe wird zur Laufzeit aus der Akzentfarbe abgeleitet, mit einer Zeile CSS pro Kategorie.

Warum es nicht reicht, den Farbton in HSL zu drehen

Die naheliegende Idee: die Akzentfarbe nehmen und ihren Farbton drehen, hsl(from var(--accent) calc(h + 120) s l). Die Farbtöne ändern sich, die wahrgenommene Helligkeit bleibt aber nicht gleich. HSL-Helligkeit ist eine Eigenschaft des RGB-Würfels, nicht des menschlichen Auges: Bei gleicher HSL-Helligkeit wirkt ein Gelbgrün viel heller als ein Blauviolett. Im Beispiel unten startet die Drehung beim time-cockpit-Blau, auf dem ein dunkles Label 6,2 : 1 erreicht. Auf dem Gelbgrün (−120°) sind es 10,9 : 1, auf dem Blauviolett (+60°) nur 2,4 : 1, deutlich unter dem WCAG-Minimum von 4,5 : 1. Mit dem dunkleren Blau und weißem Label (zweite Reihe) ist es nicht besser: Die Spanne reicht von 3,1 bis 13,0 : 1.

HSL · S 71 % · L 50 % · dunkles Label

Label 0° 6,2 : 1
Label +120° 4,1 : 1 (unter 4,5 : 1)
Label −120° 10,9 : 1
Label +60° 2,4 : 1 (unter 4,5 : 1)
Label +180° 4,9 : 1
Label −60° 9,8 : 1

HSL · S 100 % · L 31 % · weißes Label

Label 0° 5,4 : 1
Label +120° 7,7 : 1
Label −120° 3,1 : 1 (unter 4,5 : 1)
Label +60° 13,0 : 1
Label +180° 7,4 : 1
Label −60° 3,5 : 1 (unter 4,5 : 1)
Farbton in HSL gedreht, Sättigung und Helligkeit unverändert. Oben ausgehend vom time-cockpit-Blau #25a0da mit dunklem Label (#10161c), unten ausgehend vom dunkleren Blau #00729f mit weißem Label. Darunter jeweils der Kontrast des Labels.

OKLCH löst das. Die drei Kanäle sind Helligkeit (L), Buntheit (C, Chroma) und Farbton (h), und L ist so entworfen, dass es der wahrgenommenen Helligkeit entspricht. Wenn Sie L und C festhalten und nur h ändern, wirken alle Farben etwa gleich hell und gleich gesättigt. Keine Kategorie wirkt wichtiger als eine andere, und dieselbe Label-Farbe funktioniert auf allen – mit einer Einschränkung, zu der wir noch kommen. Mit derselben Ausgangsfarbe liegt jetzt jedes Feld zwischen 5,6 und 6,4 : 1.

OKLCH · L 0.67 · C 0.13 · dunkles Label

Label 0° 6,2 : 1
Label +120° 5,6 : 1
Label −120° 6,2 : 1
Label +60° 5,7 : 1
Label +180° 5,8 : 1
Label −60° 6,4 : 1

OKLCH · L 0.52 · C 0.135 · weißes Label

Label 0° 5,4 : 1
Label +120° 5,9 : 1
Label −120° 5,4 : 1
Label +60° 5,8 : 1
Label +180° 5,8 : 1
Label −60° 5,2 : 1
Dieselben Ausgangsfarben, Farbton in OKLCH gedreht. Oben mit Helligkeit und Buntheit des time-cockpit-Blaus und dunklem Label, unten mit der festen Helligkeit des Musters und weißem Label. Darunter jeweils der Kontrast des Labels.

Die Felder sind jetzt gleichmäßig, aber das Blau ist für weiße Schrift zu hell. Das Muster übernimmt deshalb nur den Farbton der Akzentfarbe und setzt Helligkeit und Buntheit auf feste Werte, L 0.52 und C 0.135 (zweite Reihe). Weiße Labels erreichen hier 5,2 bis 5,9 : 1. Weil L nicht mehr von der Akzentfarbe kommt, hängt die Lesbarkeit auch nicht davon ab, wie hell die Marke eines Kunden ist: Rechnerisch bleibt jeder Farbton bei mindestens 5,1 : 1. Warum es gerade L 0.52 ist und was der Browser tatsächlich daraus macht, zeigt Falle 2 weiter unten.

Der Preis: weniger Leuchtkraft

Neben der zweiten HSL-Reihe wirkt die OKLCH-Palette matter. Das liegt nicht an OKLCH, sondern an der Entscheidung für gleiche Helligkeit. HSL mit S 100 % schiebt jede Farbe an den Rand dessen, was sRGB darstellen kann, und zwar bei der Helligkeit, die dieser Farbton in HSL gerade hat. Bei fester Helligkeit liegt dieser Rand je nach Farbton verschieden weit draußen:

FarbtonAbstandGrößtes C in sRGB bei L 0.52C im MusterHSL-Reihe: LHSL-Reihe: C
Blau0°0.1110.1110.520.110
Pink+120°0.2130.1350.470.199
Gelbgrün−120°0.1180.1180.650.167
Violett+60°0.2850.1350.340.209
Orange+180°0.1280.1280.470.157
Türkis−60°0.0980.0980.610.190

Die Spalte „C im Muster“ zeigt, was aus C 0.135 tatsächlich wird. Blau, Gelbgrün, Orange und Türkis stoßen bei L 0.52 schon vorher an die Grenze von sRGB und verlieren Buntheit, bis sie darstellbar sind. Ganz gleich bunt ist die Palette also nicht. Wirklich gleiche Buntheit für jede beliebige Akzentfarbe gäbe es bei L 0.52 nur bis etwa C 0.09, und das wäre deutlich matter. C 0.135 ist ein Kompromiss. Pink und Violett dagegen könnten 1,6- bzw. 2,1-mal so bunt sein.

Die HSL-Reihe profitiert von ihren Helligkeitsunterschieden. Ihr Gelbgrün liegt bei L 0.65, ihr Türkis bei L 0.61, und dort lässt sRGB mehr Buntheit zu. Manche Farbtöne leuchten überhaupt nur hell: Gelbgrün ist am buntesten bei L 0.95, Türkis bei L 0.89. Bei L 0.52 wird aus Gelb zwangsläufig Oliv. Genau diese Helligkeitsunterschiede machen in HSL aber die Labels unlesbar. Dazu kommt: Gleiche Abstände in HSL sind in OKLCH keine gleichen Abstände. Das HSL-„Türkis“ bei −60° hat in OKLCH den Farbton 145° und ist eigentlich ein Grün.

Wer mehr Leuchtkraft will, kann jedem Farbton dieselbe Helligkeit lassen, die Buntheit aber an seinem eigenen Maximum ausrichten statt an einem gemeinsamen Wert, ähnlich wie die Sättigung in OKHSL. Die untere Reihe nutzt für jeden Farbton das volle Maximum. Die Helligkeit bleibt gleich, und weiße Labels erreichen weiterhin mindestens 5,2 : 1. Es ändern sich nur Pink und Violett, und die beiden leuchten nicht nur, sie drängen sich auch vor:

OKLCH · L 0.52 · C 0.135 · weißes Label

Label 0° 5,4 : 1
Label +120° 5,9 : 1
Label −120° 5,4 : 1
Label +60° 5,8 : 1
Label +180° 5,8 : 1
Label −60° 5,2 : 1

OKLCH · L 0.52 · größtes C je Farbton · weißes Label

Label 0° 5,4 : 1
Label +120° 6,2 : 1
Label −120° 5,4 : 1
Label +60° 6,5 : 1
Label +180° 5,8 : 1
Label −60° 5,2 : 1
Oben das helle Band mit fester Buntheit, unten dieselben Farbtöne mit der größten Buntheit, die sRGB bei L 0.52 zulässt. Nur Pink und Violett ändern sich, die anderen vier sind schon oben am Rand von sRGB. Darunter jeweils der Kontrast des Labels.

Mit einer Zeile CSS ist das allerdings nicht zu machen, denn die Relative Color Syntax kennt das Maximum eines Farbtons nicht. Die Werte müssen per JavaScript berechnet werden. Man könnte C auch absichtlich zu hoch setzen und sich darauf verlassen, dass der Browser die Buntheit bis zum Rand senkt. Chromium schneidet aber stattdessen die RGB-Kanäle ab und verschiebt dabei Farbton und Helligkeit (siehe Falle 2).

Auf Bildschirmen mit P3-Farbraum, etwa aktuellen Macs und Handys, ist mehr Platz. Beim time-cockpit-Blau passen dort fünf der sechs Farbtöne mit C 0.135 hinein, das Türkis fast (C 0.133), und der Browser zeigt sie ohne Abstriche. Dieselbe Palette sieht auf P3- und sRGB-Bildschirmen also leicht unterschiedlich aus. Die Beispiele hier sind sRGB-Werte und zeigen das nicht.

Zum Ausprobieren: HSL und OKLCH im Vergleich

Wählen Sie eine Ausgangsfarbe, etwa Ihre eigene Markenfarbe, und ändern Sie mit den Reglern ihre Sättigung und Helligkeit. Mit dem dritten Regler legen Sie fest, wie viele Farben entstehen, von 4 bis 12, und die Textfarbe auf den Feldern können Sie selbst wählen. Anders als im Muster liegen die Farbtöne hier gleichmäßig verteilt und stehen in der Reihenfolge nebeneinander, in der sie auf dem Farbkreis aufeinander folgen. So sehen Sie, wie ähnlich sich Nachbarn werden, je mehr Farben es sind. Außerdem behalten die OKLCH-Reihen hier die Helligkeit der Ausgangsfarbe. Bei fast jeder Einstellung liegen die Kontrastwerte der OKLCH-Reihe enger beisammen als die der HSL-Reihe. Die dritte Reihe gibt jedem Farbton denselben Anteil an seiner größtmöglichen Buntheit, den die Ausgangsfarbe an ihrer hat.

Textfarbe

HSL gedreht · S 71 % · L 50 % dunkles Label · Kontrast 2,4 – 10,9 : 1

Aa 0° 6,2
Aa +60° 2,4 (unter 4,5 : 1)
Aa +120° 4,1 (unter 4,5 : 1)
Aa +180° 4,9
Aa −120° 10,9
Aa −60° 9,8

OKLCH gedreht · L 0.67 · C 0.132 dunkles Label · Kontrast 5,6 – 6,4 : 1

Aa 0° 6,2
Aa +60° 5,7
Aa +120° 5,6
Aa +180° 5,8
Aa −120° 6,2
Aa −60° 6,4

OKLCH, relative Buntheit · L 0.67 · C 93 % des Maximums dunkles Label · Kontrast 5,4 – 6,4 : 1

Aa 0° 6,2
Aa +60° 5,6
Aa +120° 5,4
Aa +180° 5,8
Aa −120° 6,2
Aa −60° 6,4
Mit dem Farbwähler wählen Sie die Ausgangsfarbe, die Regler ändern ihre Sättigung und Helligkeit in HSL und die Zahl der Farben. Die Farbtöne liegen gleichmäßig verteilt auf dem Farbkreis und stehen in der Reihenfolge nebeneinander, in der sie dort aufeinander folgen. Oben wird der Farbton in HSL gedreht, in der Mitte in OKLCH mit Helligkeit und Buntheit der Ausgangsfarbe. Unten bekommt jeder Farbton denselben Anteil an seiner größtmöglichen Buntheit wie die Ausgangsfarbe. Die Textfarbe ist wählbar; automatisch bekommt jede Reihe die, weiß oder dunkel, mit der ihr schwächstes Feld besser lesbar ist. Unter jedem Feld steht sein Kontrast zum Label. Farben außerhalb von sRGB verlieren Buntheit, bis sie darstellbar sind.

Das Muster: eine Custom Property pro Kategorie

Mit der CSS Relative Color Syntax lässt sich eine vorhandene Farbe in ihre Kanäle zerlegen und daraus eine neue bauen. Mit oklch(from …) lesen Sie den Farbton der Akzentfarbe und ersetzen Helligkeit und Buntheit durch feste Werte:

:host {
  --app-hue-0: oklch(from var(--accent-base) 0.52 0.135 h);
  --app-hue-1: oklch(from var(--accent-base) 0.52 0.135 calc(h + 120));
  --app-hue-2: oklch(from var(--accent-base) 0.52 0.135 calc(h - 120));
  --app-hue-3: oklch(from var(--accent-base) 0.52 0.135 calc(h + 60));
  --app-hue-4: oklch(from var(--accent-base) 0.52 0.135 calc(h + 180));
  --app-hue-5: oklch(from var(--accent-base) 0.52 0.135 calc(h - 60));
  --app-ink: #fff;
}

In diesen sechs Zeilen stecken drei Entscheidungen:

  • Die erste Kategorie ist der Farbton der Akzentfarbe selbst. Die Marke des Kunden taucht im Chart auf, statt mit ihm zu konkurrieren.
  • Nachbarn liegen weit auseinander. Unsere Balken sind nach Größe sortiert, Kategorie 0 liegt also neben 1, 1 neben 2 und so weiter. Die Reihenfolge Akzent, +120°, −120°, +60°, +180°, −60° hält zwischen zwei Nachbarn immer mindestens 120° Abstand. Die ersten drei Farbtöne teilen den Kreis in Drittel, die letzten drei füllen die Lücken.
  • Helligkeit und Buntheit sind Konstanten. L 0.52 ist dunkel genug für weiße Labels, auch wenn der Browser die Farben abschneidet (Falle 2), C 0.135 bunt genug, um die Farbtöne zu unterscheiden, ohne zu schreien.

Verwandte Kategorien lassen sich auch bewusst zu Paaren machen. Für die KI-Kostenbalken in time cockpit teilen wir die Kosten in vier Token-Arten: Input, Output, Cache geschrieben und Cache gelesen. Output bekommt den Farbton der Akzentfarbe, Input liegt 45° dahinter. Die beiden Cache-Arten sind dasselbe Paar, um −120° gedreht. Input und Output lesen sich als eine Familie, die Caches als eine andere, und trotzdem hat jede Art ihre eigene Farbe.

Dark Mode: ein zweites Band, dunkle Schrift

Auf dunklem Hintergrund wirken dieselben Farben bei L 0.52 trüb. Der Dark Mode bekommt deshalb ein eigenes Band mit denselben Farbton-Abständen: höhere Helligkeit, etwas weniger Buntheit und dunkle Schrift auf den Balken statt weißer.

:host-context(html.dark-mode) {
  --app-hue-0: oklch(from var(--accent-base) 0.76 0.115 h);
  --app-hue-1: oklch(from var(--accent-base) 0.76 0.115 calc(h + 120));
  /* … dieselben Abstände wie im hellen Band … */
  --app-ink: #10161c;
}

Die Kategorien bleiben wiedererkennbar: Farbton 1 im Dark Mode ist derselbe Farbton wie Farbton 1 im hellen Modus, nur heller. Im Beispiel stehen beide Bänder auf dunklem Hintergrund:

Helles Band (L 0.52) · weißes Label

Label 0° 5,4 : 1
Label +120° 5,9 : 1
Label −120° 5,4 : 1
Label +60° 5,8 : 1
Label +180° 5,8 : 1
Label −60° 5,2 : 1

Dunkles Band (L 0.76 · C 0.115) · dunkles Label

Label 0° 8,6 : 1
Label +120° 8,1 : 1
Label −120° 8,7 : 1
Label +60° 8,2 : 1
Label +180° 8,2 : 1
Label −60° 8,9 : 1
Beide Bänder auf dunklem Hintergrund. Das helle Band bleibt lesbar, wirkt aber dunkel und trüb; das dunkle Band hat dieselben Farbtöne bei höherer Helligkeit. Darunter jeweils der Kontrast des Labels.

Drei Fallen, mit Zahlen

Das Muster ist kurz, aber man glaubt leicht, dass es mehr leistet, als es tut. Bevor wir den Produktcode angefasst haben, haben wir eine kleine HTML-Workbench gebaut: eine Akzentfarben-Auswahl, Schieberegler für die Farbton-Abstände und für L und C, eine WCAG-Kontrasttabelle für jede Farbe und den OKLab-Abstand benachbarter Farben. Drei Dinge haben wir dabei gelernt.

Falle 1: Gleiche OKLCH-Helligkeit ist nicht gleicher WCAG-Kontrast

OKLCH-Helligkeit und WCAG-Kontrast messen Verschiedenes. Der WCAG-2-Kontrast beruht auf der relativen Luminanz, berechnet aus sRGB, und das Wahrnehmungsmodell hinter OKLab folgt nicht derselben Kurve. Bei gleichem L haben ein Grün und ein Violett gegen Weiß trotzdem unterschiedlichen Kontrast.

Für sechs Farbtöne bei L 0.52 und C 0.135 mit weißen Labels haben wir diese Bereiche berechnet:

AkzentfarbeWeiße Labels, helles BandDasselbe, Kanäle abgeschnitten wie in ChromiumFarbtöne außerhalb von sRGB (helles Band)Dunkle Labels (#10161c), dunkles Band
#25a0da5,2 – 5,9 : 14,9 – 5,9 : 14 von 68,1 – 8,9 : 1
#9b2fae5,2 – 5,9 : 14,8 – 5,9 : 12 von 68,1 – 8,9 : 1
#c8102e5,2 – 5,9 : 14,8 – 5,9 : 12 von 68,1 – 8,8 : 1
#2e8b575,2 – 6,0 : 14,9 – 6,0 : 13 von 68,1 – 8,9 : 1

Bei derselben Helligkeit liegen die Farbtöne also bis zu einem ganzen Kontrastpunkt auseinander, abgeschnitten etwa 4,8 gegenüber 5,9 : 1. Wählen Sie L für den schlechtesten Fall, nicht für den Durchschnitt: Jede Kombination muss bei mindestens 4,5 : 1 bleiben, dem WCAG-Minimum für normalen Text.

Falle 2: Manche Farbtöne gibt es in sRGB nicht

Bei Chroma 0.135 liegen für typische Akzentfarben zwei bis vier der sechs Farbtöne außerhalb des sRGB-Farbraums, beim time-cockpit-Blau vier. Der Browser scheitert nicht, macht aber nicht unbedingt das, was man erwartet. CSS Color 4 beschreibt ein Gamut Mapping, das Helligkeit und Farbton hält und nur die Buntheit senkt. Chromium schneidet in unserer Messung stattdessen jeden RGB-Kanal einzeln ab. Dabei verschieben sich auch Farbton und Helligkeit: Das Blau wandert von 235° auf 241°, das Türkis von 175° auf 171° und von L 0.52 auf L 0.537. Das Chart funktioniert weiter, aber diese Farben sind nicht ganz die, die im CSS stehen, und ihr Kontrast verschiebt sich. Genau deshalb steht im Muster L 0.52. Mit unserem ersten Wert, L 0.54, fiel das abgeschnittene Türkis auf 4,49 : 1, knapp unter die WCAG-Grenze. Über alle Akzentfarben gerechnet sank der schlechteste Wert sogar auf 4,4 : 1, bei Farbtönen zwischen 174° und 207°. Bei L 0.52 bleibt jeder Farbton auch abgeschnitten bei mindestens 4,8 : 1. Messen Sie das gerenderte Ergebnis, statt der Formel zu vertrauen, und senken Sie L oder C, bis der schlechteste Fall passt.

Falle 3: Ein Invert-Filter verzerrt Ihre Palette

Die Web-App von time cockpit setzt den Dark Mode um, indem sie die ganze Seite mit filter: invert(1) hue-rotate(180deg) invertiert. Das ist billig und funktioniert für Text und Rahmen erstaunlich gut. Für eine abgestimmte Palette nicht: Der Filter dreht die Helligkeit um und hält den Farbton ungefähr, rechnet dabei aber in sRGB, nicht in OKLCH. Aus sechs Farben mit gleicher Helligkeit L 0.52 werden Farben zwischen L 0.69 und L 0.76, das Olivgrün deutlich dunkler als das Rosa. Die gleichmäßige Palette ist weg, und das dunkle Band, das Sie für den Dark Mode entworfen haben, kommt nie zum Vorschein.

Helles Band, wie entworfen

Label 0° L 0.52
Label +120° L 0.52
Label −120° L 0.52
Label +60° L 0.52
Label +180° L 0.52
Label −60° L 0.52

Dasselbe nach invert(1) hue-rotate(180deg)

Label 0° L 0.73
Label +120° L 0.76
Label −120° L 0.69
Label +60° L 0.72
Label +180° L 0.74
Label −60° L 0.72

Dunkles Band, wie entworfen

Label 0° L 0.76
Label +120° L 0.76
Label −120° L 0.76
Label +60° L 0.76
Label +180° L 0.76
Label −60° L 0.76
Oben das helle Band, in der Mitte dasselbe nach dem Invert-Filter des Dark Mode: Die Helligkeit wird umgedreht, aus weißen Labels werden schwarze, der Farbton bleibt ungefähr. Unten das dunkle Band, das für den Dark Mode entworfen ist. Darunter jeweils die OKLCH-Helligkeit des Feldes.

Die Chart-Elemente werden deshalb aus der Invertierung herausgenommen. Sie tragen eine Klasse, die denselben Filter ein zweites Mal anwendet und damit den ersten weitgehend aufhebt, und sie verwenden das dunkle Band von oben:

html.dark-mode {
  filter: invert(1) hue-rotate(180deg);
}

html.dark-mode .no-dark-invert {
  filter: invert(1) hue-rotate(180deg);
}

Ganz exakt ist das nicht, weil der Browser nach jedem Filterdurchgang auf den sRGB-Bereich abschneidet. Das Türkis des dunklen Bands kommt als #57c4a9 statt #51c9ad heraus, die anderen fünf Farben bleiben praktisch unverändert. Wenn Ihr Dark Mode mit echten Farb-Tokens statt mit einem Filter gebaut ist, haben Sie dieses Problem nicht. Wenn er mit einem Filter gebaut ist, prüfen Sie jedes farbige Element.

Fallbacks für ältere Browser

Die Relative Color Syntax wird von allen aktuellen Evergreen-Browsern unterstützt, von älteren nicht. Der übliche Trick, eine Eigenschaft zweimal zu deklarieren und den Browser die Version verwerfen zu lassen, die er nicht versteht, funktioniert hier nicht: Custom Properties akzeptieren beim Parsen jeden Wert, der Browser behält also die relative Farbe und scheitert erst, wenn er var(--app-hue-1) auflöst. Der Balken steht dann ohne Hintergrund da.

Der Fallback braucht deshalb eine explizite Feature Query. Wir setzen zuerst feste Hex-Werte für die Standard-Akzentfarbe und überschreiben sie nur dort, wo Relative Colors funktionieren. Die Query muss dabei genau die Schreibweise prüfen, die Sie verwenden: Safari 16.4 bis 17 kennt oklch(from …) schon, verlangt beim Farbton aber eine Einheit und verwirft calc(h + 120). Ein Test auf oklch(from red l c h) würde dort bestehen, und alle Balken außer dem ersten stünden ohne Hintergrund da. Deshalb prüfen wir eine Rechnung mit h mit:

:host {
  --app-hue-0: #00729f;
  --app-hue-1: #a2426c;
  /* … */

  @supports (color: oklch(from red l c calc(h + 1))) {
    --app-hue-0: oklch(from var(--accent-base) 0.52 0.135 h);
    --app-hue-1: oklch(from var(--accent-base) 0.52 0.135 calc(h + 120));
    /* … */
  }
}

Kunden mit einem alten Browser sehen die Standard-Palette statt ihrer eigenen. Das ist ein fairer Tausch.

Wo wir das einsetzen

In time cockpit zeichnen der Zeitkalender (Angular) und der Desktop-Signal-Tracker (Electron) KI-Kostenbalken, aufgeteilt nach Token-Art, und einen Anwendungsbalken, aufgeteilt nach Anwendung. Alle diese Farben werden aus der Akzentfarbe des Tenants abgeleitet, im hellen wie im dunklen Modus. Die sechs größten Anwendungen bekommen die sechs Farbtöne, alles andere ist grau.

Wann Sie es nicht tun sollten

  • Mehr als etwa sechs Kategorien. Bei festem L und C bleibt nur der Farbton, um Kategorien zu unterscheiden. Mit sechs Farben liegen Nachbarn auf dem Farbkreis 60° auseinander, mit zwölf nur noch 30°. Um Blau und Türkis, wo sRGB wenig Buntheit zulässt, rücken sie zusätzlich zusammen. Geben Sie dem Rest ein neutrales Grau.
  • Wenn die Farbe allein die Kategorie verrät. Gleiche Helligkeit bedeutet: In Graustufen sehen alle Kategorien fast gleich aus, und Menschen mit Farbsehschwäche fehlt der Helligkeitsunterschied, der ihnen sonst hilft. Bei simulierter Grünblindheit (Deuteranopie, eine Form der häufigsten Farbsehschwäche) sind in unserer Palette Pink und Türkis sowie Gelbgrün und Orange praktisch nicht mehr zu unterscheiden. Auch untereinander haben benachbarte Segmente nur etwa 1,1 : 1 Kontrast. WCAG 1.4.11 verlangt für Grafiken, die man zum Verständnis braucht, 3 : 1 gegen angrenzende Farben. Trennen Sie Segmente deshalb mit einer schmalen Lücke in der Hintergrundfarbe (gegen Weiß hat jeder Farbton mindestens 4,8 : 1) und beschriften Sie sie direkt, statt sich nur auf eine Legende zu verlassen.
  • Farben mit fester Bedeutung. Wenn Rot Fehler und Grün erledigt bedeutet, dürfen sich diese Farben nicht mit der Marke drehen. Halten Sie semantische Farben aus der abgeleiteten Palette heraus. Das gilt auch umgekehrt: Bei einer grünen Markenfarbe ist schon die erste Kategorie grün und liest sich schnell als „erledigt“.
  • Wenn niemand den Kontrast prüft. Das Muster macht die Palette gleichmäßig, nicht automatisch barrierefrei. Sie müssen aber nicht jede Akzentfarbe einzeln prüfen: Eine Akzentfarbe verschiebt die sechs Farbtöne nur auf dem Farbkreis. Prüfen Sie einmal alle 360 Farbtöne bei Ihrem L und C, so wie der Browser sie tatsächlich darstellt. Dann gilt das Ergebnis für jede Akzentfarbe.
  • Akzentfarben ohne Farbton. Weiß, Grau und Schwarz haben keine Buntheit und damit keinen sinnvollen Farbton. Chromium liefert für #808080 und für Weiß etwa 24°, die ganze Palette beginnt dann bei einem Rotbraun, das mit der Marke nichts zu tun hat. Fangen Sie solche Akzentfarben ab, bevor sie ins CSS kommen, und setzen Sie einen festen Standard-Farbton.

Fazit

Halten Sie OKLCH-Helligkeit und -Buntheit fest, drehen Sie nur den Farbton relativ zur Akzentfarbe, halten Sie Nachbarn mindestens 120° auseinander und geben Sie dem Dark Mode ein eigenes Band. Prüfen Sie dann Kontrast und Gamut mit echten Zahlen, statt dem Farbraum zu vertrauen.

Wenn Sie Fragen zu Theming oder Design-Systemen in Ihrer eigenen Webanwendung haben, schreiben Sie uns.