Cached at:
07/15/26, 01:39 AM
# Generative colors with CSS
Source: [https://gomakethings.com/articles/generative-colors-with-css/](https://gomakethings.com/articles/generative-colors-with-css/)
I just updated the CSS for my website to use a fork of[Kelp UI](https://kelpui.com/), which includes a much more consistent and easy\-to\-maintain class system than I had before\.
***Quick aside:**if you see any bugs in your travels,[please let me know\!](https://gomakethings.com/)*
While it mostly looks the same as before, the biggest change was to the color palette\.
I’m now taking advantage of relative colors with the`oklch\(\)`CSS function to dynamically generate all of the colors used on this site from just six hex codes defined as CSS variables\.
Let’s look at how it all works\!
## The`oklch\(\)`CSS function
The`oklch\(\)`CSS function lets you create a color by defining its…
- **Lightness**\- the perceived brightness\.
- **Chroma**\- the vibrancy or saturation of the color\.
- **Hue**\- a number representing the color\.
For example, the blue I use for this website,`\#007ab8`, is represented in OKLCH as`oklch\(55\.48% 0\.131 241\.70\)`\.
What makes the`oklch\(\)`function so cool is that once you have a colors L, C, and H values, you can tweak the*lightness*and*chroma*slightly to create a full palette from your*hue*\.
For example, I could create a slightly darker shade of that blue by reducing the*lightness*slightly:`oklch\(48% 0\.131 241\.70\)`\.
***Note:**there are other color functions besides`oklch\(\)`, including`rgb\(\)`,`oklab\(\)`, and more\. I find`oklch\(\)`the easiest to work with\.*
## Relative colors
Creating a palette of colors from`oklch\(\)`by hand is tedious work, and I don’t recommend it\.
This is where[relative colors](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors/Using_relative_colors)come in\!
By passing the`from`operator followed by a color \(as a hex value*or*another color function\), the`oklch\(\)`function will convert the color into`l`,`c`, and`h`values \(with percentages represented as decimals\) for you\.
```
:root {
--color-blue: oklch(from #007ab8 l c h);
}
```
Here, I’ve passed the generated variables along as\-is without any changes\.
But—this is where things get really powerful—you can manually override their values, and even use the`calc\(\)`math function to adjust the values\.
Let’s create a darker shade of blue by adjust the*lightness*\(`l`\) down 5% programmatically\. I don’t care or need to know what the actual starting value is\.
```
:root {
--color-blue-darker: oklch(from #007ab8 calc(l - 0.05) c h);
}
```
## A quick note about lightness and chroma
Very light and very dark*lightness*levels have a tendency to look oversaturated\.
As a result, a lot of color palettes look more cohesive when you reduce the*chroma*slightly as you move away from the middle\-range of*lightness*towards the edges\.
## Generating a whole palette
Let’s look at how we can use`oklch\(\)`and a little bit of math to generate an 11\-color palette from a single hex code\.
First, we’ll define our color as a CSS variable\.
```
:root {
--color: #007ab7;
}
```
Next, we’ll create a range of named color variables, from`\-\-color\-05`to`\-\-color\-95`\.
For each one, we’ll generate the color`from`our`\-\-color`variable\. We’ll adjust the lightness \(`l`\), but keep the chroma \(`c`\) unchanged for now\.
```
:root {
--color: #007ab7;
--color-05: oklch(from var(--color) 18.5% c h);
--color-10: oklch(from var(--color) 24% c h);
--color-20: oklch(from var(--color) 32.5% c h);
--color-30: oklch(from var(--color) 40% c h);
--color-40: oklch(from var(--color) 45% c h);
--color-50: oklch(from var(--color) 57% c h);
--color-60: oklch(from var(--color) 67% c h);
--color-70: oklch(from var(--color) 75% c h);
--color-80: oklch(from var(--color) 83.5% c h);
--color-90: oklch(from var(--color) 92% c h);
--color-95: oklch(from var(--color) 96% c h);
}
```
[Here’s a demo of what the palette looks like\.](https://codepen.io/cferdinandi/pen/MYbWQxx)
## Adjusting the chroma
You may notice that the lightest shades are still pretty vibrant\. If you were using this as muted background colors, they might be a bit too intense\.
To fix that, we’ll use the`calc\(\)`function and a little math to adjust the chroma down a bit near the edges of our palette\.
After some trial\-and\-error, I found a range of percentages I like to adjust the chroma by at each step in the palette\. To make the math work, I divide the chroma \(`c`\) by`0\.2`, then multiply it by the percentage\.
Why divide by`0\.2`? In OKLCH,`0\.4`represents 100% saturation\. It’s rare for a color to have that level of saturation\. Using 50% saturation gives you a could baseline for the rest of the math\.
```
:root {
--color: #007ab7;
--color-05: oklch(from var(--color) 18.5% calc(0.08 * (c / .2)) h);
--color-10: oklch(from var(--color) 24% calc(0.1 * (c / .2)) h);
--color-20: oklch(from var(--color) 32.5% calc(0.135 * (c / .2)) h);
--color-30: oklch(from var(--color) 40% calc(0.16 * (c / .2)) h);
--color-40: oklch(from var(--color) 45% calc(0.185 * (c / .2)) h);
--color-50: oklch(from var(--color) 57% calc(0.2 * (c / .2)) h);
--color-60: oklch(from var(--color) 67% calc(0.175 * (c / .2)) h);
--color-70: oklch(from var(--color) 75% calc(0.13 * (c / .2)) h);
--color-80: oklch(from var(--color) 83.5% calc(0.085 * (c / .2)) h);
--color-90: oklch(from var(--color) 92% calc(0.04 * (c / .2)) h);
--color-95: oklch(from var(--color) 96% calc(0.02 * (c / .2)) h);
}
```
[Here’s what the palette looks like with the chroma adjusted\.](https://codepen.io/cferdinandi/pen/ogYNpaV)
Notice how the palette is more subdued, especially at the lightest and darkest shades\.
## Manual chroma override
Sometimes, the chroma of a color you’ve picked is just too muted or too vibrant and doesn’t work well\. I wanted a way to manually adjust it up or down without having to find a better hex value\.
To support that, I updated the`calc\(\)`function for the chroma to use a`\-\-chroma`variable as its starting point\.
CSS variables let you pass in a fallback value\. I use`c / \.2`as the default, and`\-\-chroma`if one is defined\.
The color blue I used,`\#007ab7`, has a`c / \.2`value of about`0\.65`\. Here, I’ve set`\-\-chroma`to`0\.95`, making it substantially brighter or more vibrant\.
```
:root {
--color: #007ab7;
--chroma: 0.95;
--color-05: oklch(from var(--color) 18.5% calc(0.08 * var(--chroma, c / .2)) h);
--color-10: oklch(from var(--color) 24% calc(0.1 * var(--chroma, c / .2)) h);
--color-20: oklch(from var(--color) 32.5% calc(0.135 * var(--chroma, c / .2)) h);
--color-30: oklch(from var(--color) 40% calc(0.16 * var(--chroma, c / .2)) h);
--color-40: oklch(from var(--color) 45% calc(0.185 * var(--chroma, c / .2)) h);
--color-50: oklch(from var(--color) 57% calc(0.2 * var(--chroma, c / .2)) h);
--color-60: oklch(from var(--color) 67% calc(0.175 * var(--chroma, c / .2)) h);
--color-70: oklch(from var(--color) 75% calc(0.13 * var(--chroma, c / .2)) h);
--color-80: oklch(from var(--color) 83.5% calc(0.085 * var(--chroma, c / .2)) h);
--color-90: oklch(from var(--color) 92% calc(0.04 * var(--chroma, c / .2)) h);
--color-95: oklch(from var(--color) 96% calc(0.02 * var(--chroma, c / .2)) h);
}
```
[Here’s what the palette looks like with the manual`\-\-chroma`override\.](https://codepen.io/cferdinandi/pen/zxoYpeL)
Feel free to play around with the`\-\-chroma`value, or even remove it altogether, and see how the color palette changes in response\.
## CSS is programming language
I hope this article makes it pretty clear that CSS is in fact a programming language, and can lots of really cool stuff that used to require JS or hand\-coding\.
This one update has made working with colors so much easier, and I’m eager to bring it to[Kelp](https://kelpui.com/)soon\.