If you opened a fresh design system repo in 2026 and saw oklch(0.62 0.22 295) instead of #a855f7, you are not alone in wondering what changed. OKLCH quietly became the default color format in Tailwind v4, the new shadcn/ui theming layer, and most of the design systems shipping at YC-stage startups this year. It is doing a job that HEX and HSL could not do well, and it is doing it in every modern browser without a polyfill.
That does not mean HEX is dead, and it does not mean you should rewrite every variable in your codebase tonight. This guide explains the three formats in plain terms, shows where each one wins, and gives you a decision framework you can apply to your next project in about thirty seconds.
Generate a complete brand palette and copy it as HEX, HSL, or OKLCH with one click — no signup, no watermark, dark-mode tokens included.
Try the Color Palette Generator free →Before we get into theory, here is the same color expressed three ways so the rest of the article has a shared reference point:
| Format | Example | What it describes |
|---|---|---|
HEX | #a855f7 | Three bytes of sRGB (red, green, blue) packed into base 16 |
HSL | hsl(271 91% 65%) | Hue angle, saturation, lightness — still sRGB underneath |
OKLCH | oklch(0.62 0.22 295) | Perceptual lightness, chroma, hue — in a wider color space |
To your eye they look identical. To the browser they are stored, interpolated, and clamped to display gamut in different ways — and that is where the practical differences start.
HEX is just a compressed way to write an sRGB triplet. #a855f7 means red = 168, green = 85, blue = 247 on a 0–255 scale. Every device, every email client, every legacy CMS knows what to do with it.
The trouble is that HEX is opaque to humans. If a stakeholder asks "can you make it slightly darker?" you have no idea which byte to nudge. You end up bouncing into a color picker, eyeballing it, copying a new hex back out. That is fine for a single brand color, painful for a 12-step ramp, and disastrous for generating themes programmatically.
HEX also has no notion of brightness the way humans see it. #ffff00 (yellow) and #0000ff (blue) both have one channel maxed out, but yellow looks roughly seven times brighter to the human eye. That mismatch is why naive "lighten by 10%" functions in HEX produce dirty, washed-out results.
HSL (hue, saturation, lightness) was added to CSS in 2011 and felt like a revelation. Suddenly you could write hsl(271 91% 65%) and a teammate could read it: "purple-ish, very saturated, medium-light." Changing the lightness from 65% to 45% gives you a darker shade of the same hue. Most design systems built between 2014 and 2022 are quietly HSL underneath.
The problem is that HSL lies about lightness. The "L" channel is mathematically simple but perceptually wrong. Compare these three colors, all at lightness 50%:
To HSL, they are equally light. To your eye, the yellow is blinding, the green is bright, and the blue is dark. That difference is why a 10-step HSL ramp from L: 95% down to L: 5% produces a smooth gray ramp but a wildly uneven color ramp — the yellows clump at the light end, the blues clump at the dark end, and nothing in the middle feels evenly spaced.
The other thing HSL gets wrong is gamut. It is locked to sRGB, which covers maybe 70% of what a modern P3 display can actually show. The reds in your iPhone photos are more saturated than anything HSL can describe.
OKLCH stands for Oklab Lightness, Chroma, Hue. Oklab is a color space designed by Börn Ottosson in 2020 specifically to fix the "L means something" problem — if two OKLCH colors have the same L, they actually look equally bright to a typical observer.
The three channels mean:
0.5 is a medium gray that genuinely sits halfway between black and white.0.37; wide-gamut displays can push higher.Because L is perceptually uniform, you can build a 10-step ramp by walking from L: 0.95 down to L: 0.15 in equal increments and get a ramp that looks evenly spaced — in any hue, on any display. That is the property that makes OKLCH ideal for design systems.
/* A perceptually even purple ramp in OKLCH */
--purple-50: oklch(0.97 0.02 295);
--purple-100: oklch(0.93 0.05 295);
--purple-200: oklch(0.86 0.10 295);
--purple-300: oklch(0.77 0.16 295);
--purple-400: oklch(0.69 0.20 295);
--purple-500: oklch(0.62 0.22 295);
--purple-600: oklch(0.54 0.22 295);
--purple-700: oklch(0.46 0.20 295);
--purple-800: oklch(0.38 0.16 295);
--purple-900: oklch(0.28 0.10 295);
Notice that only the L and C numbers move. Hue stays locked at 295. To make a teal ramp with identical perceptual spacing, you change one number: 295 → 195. Try doing that in HEX.
OKLCH is not tied to sRGB. When the browser renders oklch(0.62 0.30 25) on a P3-capable screen, it uses the wider gamut and you get a richer, more saturated red than any HEX value can express. On an older sRGB screen, the browser clamps it down gracefully. You write one color and get the best version your hardware can render — no media queries needed.
| Format | Chrome / Edge | Safari | Firefox | Old browsers |
|---|---|---|---|---|
HEX | Forever | Forever | Forever | Forever |
HSL | Since 2011 | Since 2011 | Since 2011 | IE9+ |
OKLCH | 111+ (Mar 2023) | 15.4+ (Mar 2022) | 113+ (May 2023) | No fallback in IE |
Global support for OKLCH crossed 93% in late 2024 and is at roughly 96% as of Q2 2026 (caniuse). The remaining gap is mostly Android WebView versions older than 113 and Samsung Internet under 22. If your analytics show fewer than 1% of users on those, you can ship OKLCH without a fallback. Otherwise, modern build tools (Tailwind v4, PostCSS Preset Env, Lightning CSS) will compile oklch() down to the nearest #hex for you at build time.
This is the practical reason to care. A palette generator using HSL has to add hue-specific magic numbers to fake perceptual uniformity. A palette generator using OKLCH just walks the L axis.
Here is the entire algorithm for a balanced 9-step accent ramp in OKLCH:
const hue = 295; // purple
const chroma = 0.22; // saturation
const steps = [0.95, 0.85, 0.75, 0.65, 0.55,
0.45, 0.35, 0.25, 0.15];
steps.map((L, i) => `--accent-${(i+1)*100}: oklch(${L} ${chroma * Math.min(1, L * 1.6)} ${hue});`);
Twelve lines of code, applied to any hue, produces a Tailwind-quality ramp. The same algorithm in HSL needs a lookup table of hue corrections to stop the yellows from blowing out.
Pick a brand color, hit generate, and copy a full perceptually-balanced palette in OKLCH, HSL, or HEX. Includes dark-mode tokens and WCAG contrast checks.
Generate a palette →OKLCH is the right default for new design tokens, but HEX is not going anywhere. Here are the places where HEX is still the correct choice:
fill="#a855f7" not fill="oklch(...)".If your codebase is already on HSL CSS variables, you do not need a rewrite. A staged migration costs nothing and lets you A/B the result:
--accent, --bg-card, --text all stay where they are.:root block. Use a converter to get exact equivalents the first pass.Most teams report the swap takes an afternoon and produces visibly nicer dark-mode surfaces immediately, because the gray steps actually look evenly spaced for the first time.
| If you are… | Use |
|---|---|
| Authoring a new design system or theme | OKLCH |
| Generating a programmatic color ramp | OKLCH |
| Maintaining an existing HSL system | HSL (don’t migrate for its own sake) |
| Writing email HTML | HEX |
| Sharing a value with a designer or brand guide | HEX |
| Targeting users on browsers older than 2023 | HEX with a build-time fallback |
| Doing anything that touches a wide-gamut display | OKLCH |
If you want to go deeper, Börn Ottosson's original Oklab post is short and surprisingly readable. Evil Martians' OKLCH writeup has interactive visualizations that make the perceptual-uniformity claim obvious in about ten seconds. And the CSS Color Level 4 spec is where this all became official — useful when you need to argue with a senior engineer about whether OKLCH is "standard."
Generate a complete brand palette — tints, shades, dark-mode surfaces, WCAG-checked text colors — and copy the values as HEX, HSL, or OKLCH. Free, no signup.
Open the Color Palette Generator →