Skip to main content
Projects

OkLCH Color Palettes

Creating perceptually-uniform color palettes for consistent contrast in theming, brand expression, and data visualization

Role
Design Lead
When
2025—2026

Context

As the Unified Design Language evolved, we needed an extended color system with underlying logic to make palettes plug and play for any of the dozen themes.

This led me to the okLCH color space and the idea of re-balancing our colors into perceptually uniform palettes. What started as a side-of-desk project ended with 28 new color ramps, 365 colors, and a visual evolution of the brand.

Here's a high-level overview of the color system and how it works. A more in-depth post about the process is coming soon.

Overview

The primary goal was to expand our color offerings and ensure accessible contrast across themes. To that end, the resulting ramps each have steps 25 to 975 and fall within a given hue range (0 being white and 1000 black). Steps are ordered by luminance, and each step corresponds to a target contrast range.

Since steps are aligned to luminance, we can ensure consistent contrast no matter the combination.

Colors are divided into three categories: Hues (ROYGBIV & friends), Neutrals, and Alphas.

Spectrum Colors

16 chromatic ramps for primary and status slots in a theme, these represent saturated hues covering the majority of the color spectrum.

Ruby
Red
Orange
Yellow
Gold
Lime
Green
Jade
Teal
Blue
Navy
Indigo
Violet
Purple
Magenta
Pink

For these color ramps, the 500 step has minimum 5:1 contrast vs. white, a target derived from our brand teal . Each multiple of 100 has minimum AA contrast with steps five spots away in either direction, and 3:1 contrast vs. those four spots away.

25
50
100
200
300
400
500
600
700
800
900
950
975

The diagram below shows the hue range for each ramp mapped to the color wheel. Keen eyes may notice that a good number either overlap or abut their neighbors.

Diagram of color wheel with hue ranges for each ramp: red 350-7°, orange 16-33°, yellow 31-53°, gold 50-80°, lime 94-119°, green 136-151°, jade 154-180°, teal 171-193°, cyan 185-205°, blue 200-220°, indigo 224-246°, purple 247-264°, magenta 284-322°, pink 312-342°.

These "sibling" colors aren't meant to be used together; they're chosen as alternate options for common canonical status colors (red, green, blue, yellow, etc). This way you can find suitable pairings if your primary color uses a hue that's typically used for statuses.

And that leads us to the next group…which also offers variants. Nice segue!

Neutral Colors

Neutrals are for icons, text, borders, and other relatively monochromatic elements. Here we have two informal subsets: basic grays and… spicy grays.

Black
Gray
Ash
Zinc
Plum
Sand
Sage
Steel

Black is a pure gray scale from white to black. The next three have a subtle color temperature: gray is cool, zinc is warmer, and ash is a blend of the two. These have a natural teal-to-purple bias to harmonize with the default brand colors.

The latter four are more opinionated in terms of chroma: tinted with red (plum), yellow (sand), green (sage), and blue (steel). You're making a bit of a statement choosing these, but they're available to designers who want to lean into their primary hue or illustration palette, something we regularly see in more immersive K-12 products.

A key feature of the neutral ramps is their more dramatic luminance curve relative to the ROYGBIV ramps. This gives the middle steps a greater range of accessible pairing options. For example, the 500 step has 3:1 contrast with 200, instead of 100, so workhorse foreground colors remain legible on more surfaces.

NeutralsOthers0255075100255075100200300400500600700800900950975STEPL% [μ]
fig. 1 Average luminance by step

Ramps also include an extra 75 step that matches the 50 step in the saturated palettes, so that neutral component backgrounds can match their spectrum counterparts. You can maaaaaybe spot this in the chart above. This also helps balance the scale, nudging the darks lowers for a more nuanced dark mode.

Alpha Colors

Black and white aphas take the luminance curve of the black ramp and apply it to their respective alpha values.

Top: black; middle: blackAlpha on white; bottom: whiteAlpha on black

The system also currently supports an alpha ramp for the default gray, which visually mimics the opaque version.

Top: grayAlpha on white; bottom: gray

Palettes

Hues

A single step has min. 3:1 contrast vs steps four spots away, and 4.5:1 five steps away in any palette. For example, step 400 has a 3:1 contrast ratio vs. white through 100 and 4.5:1 vs. 900 to black.

ruby
25
50
100
200
300
400
500
600
700
800
900
950
975
red
25
50
100
200
300
400
500
600
700
800
900
950
975
orange
25
50
100
200
300
400
500
600
700
800
900
950
975
yellow
25
50
100
200
300
400
500
600
700
800
900
950
975
gold
25
50
100
200
300
400
500
600
700
800
900
950
975
lime
25
50
100
200
300
400
500
600
700
800
900
950
975
green
25
50
100
200
300
400
500
600
700
800
900
950
975
jade
25
50
100
200
300
400
500
600
700
800
900
950
975
teal
25
50
100
200
300
400
500
600
700
800
900
950
975
cyan
25
50
100
200
300
400
500
600
700
800
900
950
975
blue
25
50
100
200
300
400
500
600
700
800
900
950
975
navy
25
50
100
200
300
400
500
600
700
800
900
950
975
indigo
25
50
100
200
300
400
500
600
700
800
900
950
975
violet
25
50
100
200
300
400
500
600
700
800
900
950
975
purple
25
50
100
200
300
400
500
600
700
800
900
950
975
magenta
25
50
100
200
300
400
500
600
700
800
900
950
975
pink
25
50
100
200
300
400
500
600
700
800
900
950
975

Neutrals

black
25
50
75
100
200
300
400
500
600
700
800
900
950
975
gray
25
50
75
100
200
300
400
500
600
700
800
900
950
975
zinc
25
50
75
100
200
300
400
500
600
700
800
900
950
975
ash
25
50
75
100
200
300
400
500
600
700
800
900
950
975
sage
25
50
75
100
200
300
400
500
600
700
800
900
950
975
sand
25
50
75
100
200
300
400
500
600
700
800
900
950
975
plum
25
50
75
100
200
300
400
500
600
700
800
900
950
975
steel
25
50
75
100
200
300
400
500
600
700
800
900
950
975

Alpha

black-alpha
25
50
75
100
200
300
400
500
600
700
800
900
950
975
white-alpha
25
50
75
100
200
300
400
500
600
700
800
900
950
975
gray-alpha
25
50
75
100
200
300
400
500
600
700
800
900
950
975

Usage

Themes include slots for brand, neutral, info, success, warning, error, and highlight palettes. Semantic tokens like bg-primary point to aliases like colors.brand.500 so you only need to define which primitive ramp you want to use for each slot to get up and running. It looks something like this:

You can override individual tokens for specific needs but must make sure contrast is maintained, since the system assigns foreground and background tokens based on expected step values by default (the step being a proxy for luminance).

It sounds manual, but it can just be a matter of shifting a few tokens up or down the scale.

Theming

With 16 hues and 8 neutrals, we can map colors to semantic slots without having to worry about conflicts with brand colors.

Need your primary color to be red? Cool, your error colors can be pink or magenta. Or ruby or orange. Going for something more muted? Try navy for info and gold for warnings.

Most components also support raw colorScheme props to use a specific color from the system, even if it's not one of your designated slots.

Badges Callouts

Dataviz

The final piece of the puzzle is a dataviz palette designed with contrast and visual differences in mind. The range of colors now available allowed us to replace one-off or hardcoded colors with intentional choices.

Dataviz palette

Here's a little tool I made to test and ship our dataviz palettes:

Chart Colors [iframe]

Demo

For a simulated demo of the color system in action, check out the Theming Demo, or fiddle around with the theme settings in the header. I've used the same color palettes on this site, and though the components are different, it might give you a sense of how the colors work together.

Resources

If this kind of thing interests you, here are some resources I found useful when building out the color system:

Tools

Articles