Shifting Color Palettes using the LCH Color Space

Written — Updated

Confidence: Not fully confident that this is the right way to do it, but I like the results I got this time.

Update: Since originally writing this post I've developed my own tool to automate this process. Check it out at https://palettes.imfeld.dev!

Almost every web developer has heard of the RGB system of representing colors, which is a direct corollary to how colors are represented in video hardware. Many of us are familiar with HSL as well, in which a color is made up of hue (the raw color), saturation (how much white is mixed in to the color), and lightness.

These methods have served us well enough, but ultimately they are not very useful when you want to take a color value, modify it in some way, and have a good idea of how the resulting color will look. In the color world, they say that these color spaces are not perceptually uniform.

This is a well-known problem, and I most recently encountered it on the Tailwind CSS website when I was looking up how to create my own color palette in the same way that Tailwind's built-in palettes work.

Bad news: color is complicated and we've yet to find a tool that does a good job generating these sorts of color palettes. We picked all of Tailwind's default colors by hand, balancing them by eye. Sorry!

At work, our existing site uses the Angular Material Blue-Gray palette, mostly the primary 500 color. I tried copying the entire palette into my Tailwind CSS config, but as more colors were used the results looked drab. Worse, the Tailwind UI components that I have been using tend to use 600 as their primary shade, so everything was rather dark without extra work to shift the color palette.

The entire Material blue-gray palette:

So I was left with the question of how to make a palette which is consistent with the existing website, but also looks like something that I would want to use. My initial decision was just to punt on the decision and figure it out later.

A few days later, Lea Verou wrote a blog post about using the LCH color space in CSS, and also published a great color tool for playing with LCH. LCH, like the somewhat-better-known color space LAB, is a perceptually uniform color space. This means that the non-lightness values do not affect the eye's perception of a change in the lightness value (say, changing the lightness from 75% to 50%) of the color. HSL does not have this property, so the same change in lightness of two HSL color values will not generate a similar-looking difference in the colors. If you're curious about more details, Lea's post does a great job of explaining LCH and how it differs from RGB and HSL.

This seemed like the answer to my dilemna, so I gave it a try. I started with Material blue-gray 500, RGB value #607d8b:

Lea's color picker says this color has an LCH value of lch(50.534% 13.837 234.058). The first value there is the lightness, and the second and third values are the "Chroma" and "Hue," somewhat similar to the Saturation and Hue of HSL. (Again, Lea's post explains this better.)

From there, I took the Tailwind UI "indigo" color palette as my base and copied each color into the LCH color tool. Here, I only cared about getting the lightness values:

50 — 96.372% lightness
100 — 93.54% lightness
200 — 87.18% lightness
300 — 79.92% lightness
400 — 67.781% lightness
500 — 53.349% lightness
600 — 42.773% lightness
700 — 37.477% lightness
800 — 29.641% lightness
900 — 23.662% lightness

From there, I used the LCH color tool to create a palette with the Chroma and Hue value of the Material Blue-Gray 500 color, but the lightness values taken from Tailwind's Indigo palette. (Update: I’ve since found that it usually works best to also copy the chroma values from the source palette.) LCH is not yet widely supported in browsers, so I copied the RGB values out of the tool, and put the resulting palette into my Tailwind config.

lchBlueGray: {
  50: 'rgb(91.1% 96.96% 100%)',
  100: 'rgb(83.93% 94.59% 100%)',
  200: 'rgb(75.61% 87.74% 93.82%)',
  300: 'rgb(67.77% 79.75% 85.73%)',
  400: 'rgb(55.01% 66.73% 72.52%)',
  500: 'rgb(40.41% 51.84% 57.38%)',
  600: 'rgb(30.15% 41.39% 46.73%)',
  700: 'rgb(25.15% 36.32% 41.54%)',
  800: 'rgb(17.9% 29.04% 34.08%)',
  900: 'rgb(12.44% 23.68% 28.56%)',
}

And this is how it looks! I like this better because the different shades retain more of the blue of the middle color, while I didn't have to guess to make the brightness match the existing palettes that the Tailwind UI components come with.

Compared to the Material Blue-Gray palette I was coming from:

And once again, Tailwinds UI's Indigo:

Many thanks to Lea Verou for putting together such an easy-to-use tool!


Thanks for reading! If you have any questions or comments, please send me a note on Twitter or comment on dev.to.