Modern CSS Color: A Complete Guide to OKLCH, color-mix(), and Relative Color Syntax in 2026

πŸ“… May 23, 2026 ⏱ 18 min read 🏷 CSS, Color Science, Frontend

← Back to Blog  |  ColorPick App β†’

πŸ“– Table of Contents

  1. Why OKLCH? The Problem with HSL and sRGB
  2. OKLCH Basics β€” Lightness, Chroma, Hue Explained
  3. OKLCH vs HSL β€” Perceptual Differences
  4. The color-mix() Function in Practice
  5. Relative Color Syntax β€” The Game-Changer
  6. Display P3 and Wide-Gamut CSS Colors
  7. Building a Design System with OKLCH
  8. Browser Support and Fallbacks
  9. Tools and Color Pickers for OKLCH
  10. Conclusion

CSS color is undergoing its biggest transformation in a decade. The old trio of hex, rgb(), and hsl() served us well, but displays have outgrown sRGB, and our tools need to catch up. Enter CSS Color Level 4: OKLCH, color-mix(), relative color syntax, and native wide-gamut support.

By mid-2026, over 87% of browsers support OKLCH β€” including all major desktop and mobile browsers. This isn't a future technology; it's available in production today. In this guide, you'll learn how to use these modern CSS color features to build more perceptually consistent, maintainable, and vibrant design systems.

1. Why OKLCH? The Problem with HSL and sRGB

HSL seemed like a breakthrough when it arrived. Having hue, saturation, and lightness as intuitive controls was far better than memorizing hex codes. But HSL has a dirty secret: it's not perceptually uniform. Change the saturation in HSL, and the perceived brightness shifts visually even though the "lightness" value stays the same.

This is because HSL models the color space of sRGB, not the human visual system. Bright yellow appears much lighter than bright blue at the same HSL lightness value. When you try to generate a color palette by stepping HSL lightness uniformly, you get a lopsided result β€” some steps look too dark, others too light.

OKLCH solves this by modeling how humans actually perceive color. It was developed by BjΓΆrn Ottosson in 2020 and has since been adopted into the CSS specification. In OKLCH:

πŸ’‘ Key Insight: OKLCH is device-independent and covers the entire visible spectrum β€” far beyond sRGB. This means it can express colors from Display P3, DCI-P3, and even Rec. 2020 color spaces natively, without clipping or distortion.

2. OKLCH Basics β€” Lightness, Chroma, Hue Explained

Before diving into syntax, it's essential to understand what each OKLCH channel does:

Lightness (L)

Lightness in OKLCH is perceptual β€” a value of 0.5 looks like 50% brightness regardless of the hue. This is dramatically different from HSL, where hsl(60, 100%, 50%) (yellow) looks much brighter than hsl(240, 100%, 50%) (blue). In OKLCH, oklch(0.5 0.15 60) and oklch(0.5 0.15 240) will look equally bright.

Chroma (C)

Chroma is the "purity" or intensity of a color, from 0 (grayscale) upward. Unlike HSL saturation, chroma has no fixed maximum β€” it depends on the hue and the color gamut. For sRGB, maximum chroma varies from about 0.13 (blue hues) to 0.32 (reds and greens). Display P3 can reach chroma values over 0.4 for many hues.

Hue (H)

Hue works similarly to HSL: an angle from 0 to 360 on a color wheel. 0 is red, 90 is yellow-green, 180 is cyan-blue, 270 is purple, and 360 wraps back to red. Same familiar mental model, but now your other channels behave correctly.

/* OKLCH Syntax in CSS */
.example-1 { color: oklch(0.5 0.2 180); }
/* L=0.5 (medium lightness), C=0.2 (moderate chroma), H=180 (cyan) */

.example-2 { color: oklch(70% 0.3 30deg); }
/* Percentage lightness, deg hue, same effect */

.gray { color: oklch(0.5 0 0); }
/* Chroma 0 = neutral gray at 50% lightness */

Notice that to get a pure gray, you simply set chroma to 0. No fiddling with equal R/G/B values or battling HSL saturation quirks.

3. OKLCH vs HSL β€” A Side-by-Side Comparison

Let's see the practical difference. Suppose you want to create seven evenly-spaced lightness steps of a blue color for a UI scale:

Step HSL Attempt Perceptual Result OKLCH Equivalent Result
100 hsl(240, 100%, 95%) Very light (good) oklch(0.95 0.03 270) Very light βœ“
200 hsl(240, 80%, 80%) Light (good) oklch(0.8 0.08 270) Light βœ“
300 hsl(240, 70%, 65%) OK oklch(0.65 0.12 270) Medium-light βœ“
400 hsl(240, 60%, 50%) Looks very dark oklch(0.5 0.15 270) Genuine medium βœ“
500 hsl(240, 55%, 35%) Almost black oklch(0.35 0.12 270) Dark βœ“
600 hsl(240, 50%, 20%) Black with blue tint oklch(0.2 0.06 270) Very dark βœ“

In HSL, the "50% lightness" step (hsl(240, 60%, 50%)) looks closer to a 30% darkness because blue appears darker than other hues at the same lightness value. In OKLCH, every step looks like its actual lightness. The difference is immediately visible when building UI color ramps.

4. The color-mix() Function in Practice

color-mix() lets you blend two colors in any color space, with full control over mix proportions. This is a huge upgrade from working with Sass or Less mixins β€” it's native, dynamic, and can respond to CSS custom properties.

/* Basic 50/50 mix in the default (OKLCH) space */
.mixed { background: color-mix(in oklch, #ff6b6b, #4ecdc4); }

/* Specify proportions */
.warm-mix { background: color-mix(in oklch, #ff6b6b 75%, #4ecdc4 25%); }

/* Mix in different color spaces for different effects */
.srgb-mix { background: color-mix(in srgb, #ff6b6b, #4ecdc4); }
.hsl-mix  { background: color-mix(in hsl, #ff6b6b, #4ecdc4); }
.lch-mix  { background: color-mix(in lch, #ff6b6b, #4ecdc4); }

/* Mix with CSS custom properties β€” dynamic theming! */
:root { --primary: #6c5ce7; --accent: #fd79a8; }
.themed { color: color-mix(in oklch, var(--primary), var(--accent)); }
πŸ’‘ Why OKLCH is the best space for mixing: When you mix colors in sRGB, the interpolation goes through the RGB channels linearly, which produces muddy browns in the middle. Mixing in OKLCH interpolates perceptually β€” the middle of a red-to-blue gradient passes through purple, not gray-brown mud. Try it yourself: mix bright red and bright cyan in sRGB vs OKLCH and see the difference.

Practical Applications of color-mix()

/* Light/dark variants using color-mix */
.btn-primary { background: color-mix(in oklch, var(--primary), white 10%); }
.btn-primary:hover { background: color-mix(in oklch, var(--primary), black 15%); }

/* State colors from a single brand color */
.bg-error   { background: color-mix(in oklch, var(--primary), red 30%); }
.bg-success { background: color-mix(in oklch, var(--primary), green 30%); }
.bg-warning { background: color-mix(in oklch, var(--primary), yellow 40%); }

5. Relative Color Syntax β€” The Game-Changer

If you only learn one new CSS color feature this year, make it relative color syntax. This lets you take an existing color and modify specific channels β€” a task that previously required preprocessors, runtime JavaScript, or manual hex arithmetic.

Relative colors use the from keyword followed by a source color, then channel calculations:

/* Darken a color by reducing lightness */
.darkened { color: oklch(from var(--primary) calc(l - 0.15) c h); }

/* Change the hue while keeping lightness and chroma */
.hue-shifted { color: oklch(from #6c5ce7 l c calc(h + 180)); }

/* Desaturate a color (make it more gray) */
.muted { color: oklch(from #ff6b6b l calc(c * 0.3) h); }

/* Generate a lighter version with custom alpha */
.lighter { color: oklch(from var(--primary) calc(l + 0.2) c h / 0.8); }

/* Relative colors work in any supported space */
.hsl-tweak { color: hsl(from #6c5ce7 h s calc(l + 10%)); }

/* Create a full color scale from one base color */
:root { --base: oklch(0.5 0.2 270); }
--scale-100: oklch(from var(--base) 0.95 calc(c * 0.15) h);
--scale-200: oklch(from var(--base) 0.85 calc(c * 0.35) h);
--scale-300: oklch(from var(--base) 0.7 calc(c * 0.65) h);
--scale-400: oklch(from var(--base) l c h);
--scale-500: oklch(from var(--base) 0.35 calc(c * 0.8) h);
--scale-600: oklch(from var(--base) 0.2 calc(c * 0.5) h);
--scale-700: oklch(from var(--base) 0.1 calc(c * 0.2) h);

This is genuinely revolutionary for design systems. Instead of maintaining hundreds of manually chosen color tokens, you can define a handful of base colors and derive the rest mathematically. Changes propagate automatically. Want to shift your brand's entire palette by 15 degrees? Change one hue value. Need to make all backgrounds lighter? Adjust one lightness calculation.

6. Display P3 and Wide-Gamut CSS Colors

Modern displays β€” from iPhones and MacBooks to high-end Android devices and PC monitors β€” support wide color gamuts like Display P3. In 2026, over 80% of consumer devices can display colors beyond sRGB. The color() function in CSS lets you target these gamuts directly:

/* Display P3 colors in CSS */
.p3-red { color: color(display-p3 1 0.2 0.1); }
/* A vibrant red that can't be reproduced in sRGB */

.p3-green { color: color(display-p3 0.2 0.9 0.3); }
/* A vivid green far outside sRGB gamut */

.p3-blue { color: color(display-p3 0.15 0.3 0.95); }
/* A pure saturated blue impossible in sRGB */

/* Combining OKLCH and wide gamut */
.vibrant { color: oklch(0.6 0.35 140); }
/* OKLCH automatically uses the widest available gamut */

You can also use oklch() to express wide-gamut colors without specifying a gamut explicitly β€” OKLCH is device-independent and will map to the display's native gamut. If you need to stay within sRGB for older displays, include an sRGB fallback:

/* Wide gamut with sRGB fallback */
.hero-accent {
    color: #ff4a3a;
    color: color(display-p3 1 0.25 0.15);
}

/* Using @supports for progressive enhancement */
@supports (color: color(display-p3 0 0 0)) {
    .hero-accent { color: color(display-p3 1 0.25 0.15); }
}

7. Building a Design System with OKLCH

Let's put everything together into a practical design system foundation. Here's how to build a scalable, maintainable color system using OKLCH and relative colors:

:root {
    /* Base brand colors β€” single source of truth */
    --brand-hue: 270; /* Purple base */
    --brand-chroma: 0.2;
    --brand-lightness: 0.55;

    /* Primary color and its scale */
    --primary: oklch(var(--brand-lightness) var(--brand-chroma) var(--brand-hue));
    --primary-100: oklch(0.95 calc(var(--brand-chroma) * 0.1) var(--brand-hue));
    --primary-200: oklch(0.85 calc(var(--brand-chroma) * 0.3) var(--brand-hue));
    --primary-300: oklch(0.7 calc(var(--brand-chroma) * 0.6) var(--brand-hue));
    --primary-400: var(--primary);
    --primary-500: oklch(0.4 calc(var(--brand-chroma) * 0.85) var(--brand-hue));
    --primary-600: oklch(0.25 calc(var(--brand-chroma) * 0.6) var(--brand-hue));
    --primary-700: oklch(0.12 calc(var(--brand-chroma) * 0.3) var(--brand-hue));

    /* Neutral scale β€” all gray, all perceptual */
    --neutral-100: oklch(0.97 0 0);
    --neutral-200: oklch(0.92 0 0);
    --neutral-300: oklch(0.85 0 0);
    --neutral-400: oklch(0.7 0 0);
    --neutral-500: oklch(0.55 0 0);
    --neutral-600: oklch(0.4 0 0);
    --neutral-700: oklch(0.25 0 0);
    --neutral-800: oklch(0.15 0 0);
    --neutral-900: oklch(0.08 0 0);

    /* Semantic colors derived from brand */
    --success: oklch(0.55 0.18 150);
    --warning: oklch(0.7 0.15 85);
    --danger:  oklch(0.55 0.2 25);
    --info:    oklch(0.6 0.12 220);
}

/* Dark theme β€” just override lightness */
[data-theme="dark"] {
    --primary-100: oklch(0.35 calc(var(--brand-chroma) * 0.6) var(--brand-hue));
    --primary-200: oklch(0.45 calc(var(--brand-chroma) * 0.7) var(--brand-hue));
    /* ... swap the scale direction */
}

With this approach, you can rebrand an entire site by changing just the --brand-hue custom property. The entire color scale, all derived values, and semantic assignments will follow. No more hand-selecting color scales for every new project.

🎯 Try It With ColorPick

Using ColorPick you can pick any color from your screen and get its OKLCH values alongside HEX, RGB, and HSL. Perfect for experimenting with the modern CSS color features covered in this guide. Start building your OKLCH design system today!

8. Browser Support and Fallbacks

As of May 2026, browser support for modern CSS color features is excellent:

Feature Support Notes
oklch() βœ… 87%+ Chrome 111+, Safari 15.4+, Firefox 113+, Edge 111+
color-mix() βœ… 84%+ Chrome 111+, Safari 16.2+, Firefox 113+, Edge 111+
Relative Colors βœ… 82%+ Chrome 119+, Safari 16.2+, Firefox 129+, Edge 119+
color(display-p3) βœ… 72%+ Chrome 111+, Safari 15.4+, limited Firefox

For production use, provide simple fallbacks. CSS's cascade makes this straightforward β€” older browsers get the sRGB-safe value, modern browsers use the advanced color:

/* Practical fallback pattern */
.element {
    /* sRGB fallback for any browser */
    background: #6c5ce7;
    color: #1a1a2e;

    /* Modern OKLCH overrides for supporting browsers */
    background: oklch(0.55 0.2 270);
    color: oklch(0.15 0 0);
}

/* Optional: feature query for OKLCH */
@supports (color: oklch(0.5 0.1 0)) {
    .element { background: oklch(0.55 0.2 270); }
}
⚠️ Note for older systems: Safari 16.x and earlier have some limitations with calc() inside relative color syntax. Test your most critical paths. As of Safari 18 (late 2025), all features are stable across the latest versions of all major browsers.

9. Tools and Color Pickers for OKLCH

Working with OKLCH effectively requires good tools. Here are the best resources in 2026:

  1. ColorPick β€” Our own color picker tool, now with OKLCH readout. Pick any color on your screen and see its OKLCH, LCH, HSL, and HEX values simultaneously. Essential for converting existing designs to OKLCH.
  2. oklch.com β€” A dedicated OKLCH color picker and palette explorer. Excellent for visual exploration of the color space.
  3. Chrome DevTools β€” Since Chrome 111, DevTools supports OKLCH color picking directly in the Styles panel. Click any color swatch and switch the format to OKLCH in the color picker.
  4. Evil Martians' OKLCH Guide β€” The original article by Andrey Sitnik and the team behind PostCSS, still one of the best deep dives into the theory.
  5. PostCSS Preset-Env β€” Use postcss-preset-env to let PostCSS compile OKLCH back to sRGB fallbacks automatically during your build process.

The developer tooling landscape for OKLCH has matured significantly. VS Code extensions for color preview now support OKLCH, design tools like Figma can export OKLCH values, and CSS frameworks like Tailwind CSS have added OKLCH-based color palettes to their configuration system.

10. Conclusion

Modern CSS color features β€” OKLCH, color-mix(), relative color syntax, and native wide-gamut support β€” represent the most significant upgrade to web color since CSS 3 introduced rgba() and hsl() over a decade ago.

The key takeaways:

The future of CSS color is here. It's more powerful, more intuitive, and more beautiful than what we've been working with. Start exploring OKLCH today, and transform how you think about color on the web.


Ready to see OKLCH in action? Use ColorPick to pick any color on your screen and explore its OKLCH values. Build your color systems smarter in 2026.

← Back to Blog  |  ColorPick App  |  Β© 2026 ColorPick