DEV Community

Akash for MechCloud Academy

Posted on

Beyond Hex and RGB: A New World of Color with LCH, Oklab, and color-mix()

For over two decades, we've spoken the language of color to browsers using a few trusted dialects: Hex codes (#ff0000), RGB (rgb(255, 0, 0)), and more recently, HSL (hsl(0, 100%, 50%)). They get the job done, but they have a fundamental problem: they were designed for computers, not for humans.

Ask a designer to make a color "10% lighter," and they intuitively know what you mean. Ask a developer to do the same with HSL, and you might get a washed-out, gray-ish version of your color that looks nothing like you intended. This is because these color models are not perceptually uniform.

Today, this is changing. A new wave of CSS color features is here, giving us access to more vibrant colors and tools that think about color the way humans do. Get ready to meet LCH, Oklab, and the game-changing color-mix() function.

The Problem: Why HSL Fails Us

HSL (Hue, Saturation, Lightness) was a step in the right direction. It tried to give us human-friendly controls. But its "Lightness" channel is deeply flawed.

Consider these two colors in HSL:

  • hsl(240, 100%, 50%) — A vibrant, pure blue.
  • hsl(60, 100%, 50%) — A bright, almost neon yellow.

According to HSL, both have the exact same 50% lightness. But to our eyes, the yellow is dramatically brighter than the blue.

This inconsistency wreaks havoc when you try to create color palettes or gradients. Mixing two colors often results in a muddy, gray "dead zone" in the middle, because the path between them travels through a perceptually awkward part of the color space.

The Solution: Perceptually Uniform Color Spaces

Enter LCH and Oklab. These are modern color spaces designed around how humans actually perceive color. They fix the HSL problem by making their Lightness channel perceptually uniform.

This means that a change of 10 in the Lightness value will look like the same amount of change, regardless of the hue or saturation.

Meet LCH: Lightness, Chroma, Hue

The lch() function is structured like this: lch(Lightness Chroma Hue)

  • L (Lightness): A value from 0% (black) to 100% (white). This is the star of the show. lch(50% ...) will always have a similar perceived brightness to any other lch(50% ...) color.
  • C (Chroma): Represents the "amount" of color or colorfulness. 0 is gray, while 150 (or higher) is intensely vibrant.
  • H (Hue): An angle on the color wheel from 0 to 360, similar to HSL.
/* A vibrant, perceptually-correct purple */
.element {
  background-color: lch(55% 100 290);
}
Enter fullscreen mode Exit fullscreen mode

A huge bonus of LCH is that it can access colors outside of the standard sRGB gamut. With modern displays, you can now show much more vivid colors (from the Display-P3 space) that were previously impossible on the web.

Meet Oklab & Oklch: A Modern Alternative

Oklab is another, even more modern color space optimized for perceptual uniformity. It's fantastic for creating smooth, vibrant gradients that never go through a muddy phase. For web development, you'll most often use its polar coordinate version, Oklch, which works just like LCH.

/* The same purple, defined in Oklch */
.element {
  background-color: oklch(58% 0.18 290);
}
Enter fullscreen mode Exit fullscreen mode

Which should you use? For most tasks, they are both excellent. Oklch is often preferred by color experts for its superior performance in gradients and color manipulation.

The Real Game-Changer: Dynamic Color with color-mix()

Defining colors is one thing, but what about manipulating them? For years, this was the job of Sass functions (lighten(), darken()) or JavaScript. Not anymore.

The native CSS color-mix() function allows you to mix two colors together, right in your stylesheet.

Its syntax is: color-mix(in <colorspace>, <color1> <percentage>, <color2>);

Let's see it in action. How would we create a hover state that's a 90% tint of our brand color?

:root {
  --brand-purple: oklch(58% 0.18 290);
}

.button {
  background-color: var(--brand-purple);
}

.button:hover {
  /* Mix our brand color with 10% white */
  background-color: color-mix(in oklch, var(--brand-purple) 90%, white);
}
Enter fullscreen mode Exit fullscreen mode

This is huge! We can now create entire color systems from a few base tokens, without ever leaving CSS.

But here's where it all comes together. The color space you choose for color-mix() matters. Look what happens when we mix blue and yellow in the old srgb space versus the new oklch space.

/* This will have a muddy gray middle */
.gradient-srgb {
  background: linear-gradient(to right, blue, yellow);
  /* The color-mix equivalent shows the same muddy path */
  color-mix(in srgb, blue, yellow);
}

/* This will have a vibrant, clean transition */
.gradient-oklch {
  background: linear-gradient(in oklch, to right, blue, yellow);
  /* The mix is vibrant and correct */
  color-mix(in oklch, blue, yellow);
}
Enter fullscreen mode Exit fullscreen mode

By mixing in oklch, we get a beautiful, vibrant transition because the browser is mixing the colors in a perceptually uniform way.

Putting It All Together: A Practical Palette

Imagine you're building a design system. You can define a few base colors in Oklch and use color-mix() to generate the rest of your palette.

:root {
  --primary: oklch(65% 0.22 260); /* A nice blue-purple */
  --text-dark: oklch(25% 0 0);
  --text-light: oklch(95% 0 0);
  --border-color: color-mix(in oklch, var(--primary) 20%, #ccc);
}

body {
  color: var(--text-dark);
}

.card {
  border: 1px solid var(--border-color);
  background-color: color-mix(in oklch, var(--primary) 5%, white);
}

.card h3 {
  color: color-mix(in oklch, var(--primary) 80%, black);
}
Enter fullscreen mode Exit fullscreen mode

This system is readable, maintainable, and produces aesthetically pleasing, consistent results.

Final Thoughts

Modern CSS color isn't just a new syntax; it's a new way of thinking.

  • Intuitive & Perceptual: LCH and Oklab let us work with color in a way that aligns with human vision.
  • More Vibrant: We can finally break free of the sRGB gamut and use the full power of modern displays.
  • Dynamic & Scalable: color-mix() eliminates the need for preprocessor or JavaScript functions for basic color manipulation, making our design systems more robust and framework-agnostic.

Browser support is already excellent in all major browsers. It's time to leave the muddy grays of the past behind and step into a more colorful, vibrant, and intuitive future for web design. Start experimenting today

Top comments (0)