Skip to content

Tutorials

From HEX to OKLCH: A Practical Migration Guide for Design Systems

OKLCH is the perceptually uniform color space modern design systems are converging on. Here's a concrete migration path from HEX/HSL palettes.

Marco Elizalde

Marco Elizalde

· · 4 min read
OKLCH color migration guide cover

If you’ve shipped a design system in the last decade, your tokens are probably in HEX, RGB, or HSL. Those formats are familiar, supported everywhere, and they get the job done — until you try to scale a palette across hues and the result feels visually inconsistent.

OKLCH solves that. It’s the color space modern design systems are converging on, and 2026 is a sensible year to migrate. Let’s walk through what OKLCH actually is, why it matters, and how to migrate a real palette without breaking everything.

What’s wrong with HSL

HSL (Hue, Saturation, Lightness) was designed to be human-friendly. It mostly is — until you build a palette by varying Lightness across hues. A button at hsl(0, 70%, 50%) (red) looks meaningfully different in brightness from hsl(220, 70%, 50%) (blue), even though their Lightness values are identical.

HSL’s lightness axis is mathematically defined but not perceptually uniform. Your designers feel this when they say “the blue feels darker than the red, even though the spec says they’re at the same level.”

What OKLCH gets right

OKLCH (Lightness, Chroma, Hue) is built on top of OKLab, a color space designed to be perceptually uniform. Equal numeric changes correspond to equal perceived changes.

That means a button at oklch(60% 0.15 30) (a red) and one at oklch(60% 0.15 230) (a blue) actually look like the same brightness. Your scales work the way your designers expect.

CSS supports it natively in every modern browser. Tailwind v4 uses OKLCH internally for its default palette. The wider gamut also gives you access to colors HEX can’t even represent — useful when targeting modern P3 displays.

The migration in three steps

1. Convert your accent + neutral scales

Don’t migrate everything at once. Start with the two color groups that drive your design system: your accent (brand color) and your neutrals (grayscale).

For each step in the scale, convert the HEX to OKLCH using a tool like our Color Converter. Note which Lightness value the existing color produces — that’s your new anchor.

Example for a red accent scale:

/* Before */
--accent-50:  #fef2f2;   /* HSL: 0, 86%, 97% */
--accent-500: #dc2626;   /* HSL: 0, 73%, 51% */
--accent-900: #7f1d1d;   /* HSL: 0, 63%, 31% */

/* After */
--accent-50:  oklch(97.4% 0.013 17.4);
--accent-500: oklch(57.7% 0.245 27.3);
--accent-900: oklch(35.1% 0.150 27.0);

2. Build the rest of the scale from there

Once you have the existing colors converted, the magic of OKLCH is that you can derive the remaining scales mathematically:

  • Hold Hue and Chroma constant.
  • Vary Lightness in equal steps from light to dark.
  • Each step looks like an equal visual jump — which they don’t with HSL.

Your 100 / 200 / 300 / … 900 scale becomes:

--accent-100: oklch(94% 0.04 27);
--accent-200: oklch(86% 0.08 27);
--accent-300: oklch(78% 0.13 27);
--accent-400: oklch(67% 0.20 27);
--accent-500: oklch(57.7% 0.245 27.3);
--accent-600: oklch(50.5% 0.213 27.5);
--accent-700: oklch(44% 0.185 27);
--accent-800: oklch(38% 0.16 27);
--accent-900: oklch(35.1% 0.150 27.0);

That’s your new accent scale, perceptually uniform.

3. Validate contrast

Don’t trust the scale until you check WCAG contrast ratios for every text-on-background pair. OKLCH lightness is more predictable than HSL but it’s not magic — verify with the Contrast Checker.

A common surprise: some HSL-based “midpoint” scales (300, 400) pass contrast against white but the OKLCH-uniform versions don’t. That’s because the OKLCH scale corrects an inconsistency the HSL scale was hiding — the new scale is more honest about how much contrast each step actually provides.

What about HEX fallbacks?

You don’t need them for any browser shipping in 2025+. OKLCH is supported in Chrome, Edge, Safari, Firefox, and Opera. If you support older browsers, ship a @supports block:

.button {
  background: hsl(0, 73%, 51%);
  background: oklch(57.7% 0.245 27.3);
}

The second declaration wins where supported, the first wins everywhere else.

Why this matters in 2026

The real reason to migrate isn’t aesthetic. It’s that:

  • Design tools (Figma, Linear, Vercel, Stripe) are shipping OKLCH-native palettes — your design system needs to speak the same language during handoff.
  • Tailwind v4 uses OKLCH for its default palette and @theme tokens — your custom colors should match that convention to compose cleanly.
  • Wide-gamut displays (P3, Rec. 2020) reach more saturated reds and greens than sRGB hex can encode. OKLCH lets you specify and ship those.

You can keep using HEX for one-off colors and quick prototypes. But for the colors your design system depends on — your scales, your accents, your neutrals — OKLCH is where the rest of the industry is converging.

Tools you’ll use

For inspecting colors on existing live sites — to see what others are doing in OKLCH — DevZap’s CSS Inspector extracts computed colors from any element.

About the author

Marco Elizalde

Marco Elizalde

Founder & Engineer

Built DevZap because the existing dev tools didn't fit the way I actually work. 15+ years building for the web.

Ready to make your browser smarter?

Install DevZap free. Upgrade when you're ready.