Generative colors with CSS

Lobsters Hottest Tools

Summary

A tutorial on using the CSS oklch() function and relative colors to dynamically generate full color palettes from a single hex code, simplifying color management on websites.

<p><a href="https://lobste.rs/s/ipiook/generative_colors_with_css">Comments</a></p>
Original Article
View Cached Full Text

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\.

Similar Articles

Color picking Oklch for mortals

Lobsters Hottest

A guide explaining the Oklch color space in simple terms, emphasizing its perceptual uniformity for consistent color picking in design.

Experimenting with Random() in CSS

Hacker News Top

An blog post exploring the new CSS random() function, which allows setting properties to random values for creative designs, with demos and browser support details.

Algorithmic Theming Engines

Hacker News Top

The article introduces the new CSS `contrast-color()` function, which allows developers to automatically choose black or white text for accessible contrast against any background color, addressing the persistent issue of low contrast on the web without relying on JavaScript.