CSS

    Understanding oklch: Why Designers Are Ditching HEX in 2026

    A yellow and a blue at the same HSL lightness value don't look equally bright to the human eye. Here's why designers are moving color systems to oklch, and how to convert existing palettes.

    Editor: Paul RadfordJun 2, 20267 min read

    Paul Radford — Full-Stack Developer & Editor. Paul is a full-stack developer and editor of template.tips. Articles here are AI-drafted and reviewed by Paul, who knows the code well enough to catch what's wrong and cut the hype.

    Understanding oklch: Why Designers Are Ditching HEX in 2026

    oklch is a perceptually uniform color space that lets you define colors by lightness, chroma, and hue instead of arbitrary red/green/blue channels. Designers are moving to it because it makes color manipulation (lighter, more saturated, complementary) predictable and consistent, something HEX and even HSL never delivered reliably.

    What Problem Does oklch Actually Solve?

    HEX and RGB describe color in terms of how a screen mixes light, not how humans perceive it. That mismatch causes a specific, painful bug: take two colors with the same HSL lightness value, say a yellow and a blue, and they will not look equally bright to the human eye. Yellow at 50% lightness looks almost pastel; blue at 50% lightness looks dark and saturated. Any design system that generates tints and shades programmatically from HSL inherits this flaw, and the result is palettes that feel unbalanced no matter how carefully you pick the base hues.

    oklch fixes this because it is built on the OKLab color model, which was specifically designed to be perceptually uniform. A step of 10 in lightness looks like a step of 10 in lightness, regardless of hue. That single property is why oklch adoption jumped in design tooling throughout 2024 and 2025, from Tailwind CSS's v4 default palette to Figma plugins that export design tokens directly in oklch.

    The syntax looks like this:

    css
    1/* oklch(lightness chroma hue / alpha) */
    2/* lightness: 0 to 1 (or 0% to 100%) */
    3/* chroma: 0 to ~0.4 in practice, unbounded in theory */
    4/* hue: 0 to 360 degrees */
    5
    6.button {
    7 background-color: oklch(0.7 0.15 250);
    8}
    9
    10.button: hover {
    11 /* Just bump lightness, hue and chroma stay locked,
    12 so the hover state reads as "the same color, lighter" */
    13 background-color: oklch(0.78 0.15 250);
    14}

    Compare that to trying to do the same thing in HEX. You would need a color tool, a mental model of gamma curves, or a library like chroma.js just to get a hover state that does not accidentally shift the hue.

    How Do You Convert Existing HEX Values to oklch?

    You do not need to hand-convert your entire codebase. Browsers and tooling handle this for you, but it helps to understand the relationship so you can debug when something looks off.

    The most direct route in production code is to keep your existing HEX values as a fallback and layer oklch on top using @supports, or rely on the fact that CSS now lets you mix color functions freely.

    css
    1: root {
    2 /* Fallback for engines without oklch support */
    3 --brand-primary: #3b6dd6;
    4}
    5
    6@supports (color: oklch(0.7 0.15 250)) {
    7 : root {
    8 /* oklch equivalent, tuned by eye against the HEX original */
    9 --brand-primary: oklch(0.55 0.18 264);
    10 }
    11}
    12
    13.card {
    14 background-color: var(--brand-primary);
    15}

    For a one-off lookup, DevTools already does the conversion for you. Chrome's color picker (since Chrome 111) and Firefox's picker both let you cycle through HEX, RGB, HSL, and oklch representations of the same swatch. That is the fastest way to get a starting oklch value without doing math by hand.

    If you need programmatic conversion at build time, do not reinvent the formulas. The Color.js library maintained by contributors to the CSS Color spec handles OKLab/oklch conversion correctly, including the gamut mapping edge cases that trip up naive implementations.

    Why Does Chroma Behave So Strangely at the Edges?

    This is the gotcha that catches people who skim the spec and jump straight to production. Chroma in oklch is not bounded the way saturation is in HSL. In HSL, 100% saturation is always a valid, displayable color. In oklch, a chroma value of 0.3 might be perfectly renderable at one hue and lightness combination, and completely out of gamut at another.

    Out-of-gamut colors get clipped by the browser to the nearest displayable color inside the target color space (usually sRGB, unless you have specified a wider gamut with color-gamut media queries or a color() function targeting display-p3). The clipping is not always intuitive. You can end up with a color that looks visually different from what you specified, and two colors that were supposed to be a lightness step apart end up looking nearly identical because both got clipped to the same boundary.

    css
    1/* This looks like a vivid, deep blue on paper */
    2.warning-bad {
    3 background-color: oklch(0.4 0.35 260);
    4}
    5
    6/* Browser gamut-maps it down, and the visible result
    7 may be far less saturated than the numbers suggest.
    8 Test this specific value in DevTools before shipping it. */

    The practical fix is to preview your palette in the browser, not just in a color-math spreadsheet. Chrome and Safari's DevTools will show you the gamut-mapped result live. If you are generating palettes programmatically (for example, deriving 10 tints from one brand color), clamp chroma conservatively, somewhere around 0.15 to 0.2 for most UI colors, and only push toward 0.3+ for isolated accent colors you have manually verified.

    What Is the Real Browser Support Story?

    As of early 2026, oklch has shipped in every major evergreen browser. Chrome and Edge have supported it since version 111 (March 2023), Firefox since version 113 (May 2023), and Safari since 15.4 (March 2022, making Safari the earliest adopter here). You can confirm current figures any time on caniuse.com, which tracks this per browser version.

    The nuance that matters for real projects: support for the oklch() function is not the same as support for color-mix(), relative color syntax, or wide-gamut display-p3 output. Those are separate, more recent features that pair well with oklch but landed on different timelines. If you plan to build a design system around dynamic color mixing (color-mix(in oklch, ...)), verify that feature independently rather than assuming it ships wherever oklch does.

    The other thing worth checking before you rely on oklch site-wide: users on genuinely old browsers, think Safari below 15.4 or any Chromium build before 111, will see nothing render for that property if you have no fallback. Given that both of those thresholds are now roughly three years old, the audience affected is small for most consumer sites, but it is not zero for enterprise or government projects that mandate support for older managed browsers. Check your analytics before deciding fallbacks are unnecessary.

    Should You Migrate an Entire Design System Right Now?

    I would split this decision by where the color lives, not treat it as all-or-nothing.

    For anything generated programmatically, tint and shade scales, hover and active states, chart color ramps, oklch is worth adopting immediately. The perceptual uniformity genuinely eliminates a category of bugs where generated palettes look uneven, and the code to maintain is simpler because you are reasoning in lightness and chroma instead of fighting HSL's inconsistencies.

    For static, hand-picked brand colors that a designer already approved in HEX, converting them is lower priority. The visual difference between the HEX value and its oklch equivalent should be imperceptible if the conversion was done correctly, since they are just two encodings of the same point in color space. Spend your effort there on typography and layout instead, and only migrate the underlying token format when you touch that part of the system anyway.

    Where I would push hardest for early adoption is anywhere you are computing derived colors at runtime: theme generators, accessibility-driven contrast adjustments, or user-customizable branding features. That's exactly the case the CSS Color Module Level 4 specification was designed to address, and it is also the case where HSL's flaws show up most visibly to end users.

    A Practical Test Before You Commit

    Before rolling oklch into a production stylesheet, run this check: take your five most-used brand colors, convert them to oklch using DevTools or Color.js, then generate a lightness ramp (say, five steps from 0.3 to 0.9) for each one at a fixed chroma. Look at the output in an actual browser window, not a code comment, and check two things: does every step stay in gamut without visible clipping, and does the perceived brightness actually feel evenly spaced across the ramp. If both hold, you have a solid palette foundation. If a color clips or looks uneven, drop its chroma slightly and retest rather than fighting the color space, since forcing high chroma at extreme lightness values is fighting physics, not a bug in the browser.

    Related Articles