OKLCH vs HEX vs HSL: Which CSS Color Format Should You Actually Write in 2026?
Published on May 19, 2026 by The Kestrel Tools Team • 9 min read
A designer drops a Figma file in your Slack and the colors are specced as oklch(62% 0.18 264). You stare at the value for a second, then quietly change it to #3B82F6 because that’s what your design tokens have always looked like. The build passes. Nobody complains. But you have a vague feeling you just lost something in translation — and you did.
The oklch vs hex vs hsl 2026 decision is genuinely live right now in a way it wasn’t even eighteen months ago. OKLCH and the broader Level 4 color features (color-mix(), relative color syntax, wide-gamut display-p3) hit effective Baseline support in 2024, which means in 2026 you can actually ship them in production CSS without polyfills. Most ranking content on color formats predates that and still treats HSL as the modern choice.
This is the short, opinionated decision guide we wish existed: when to reach for OKLCH, when HSL is still the right tool, and when HEX is just fine. You can convert any color between HEX, RGB, HSL, and OKLCH on Kestrel Tools while you read — it runs entirely client-side, so your brand palette never leaves the browser.
OKLCH vs HEX vs HSL: which one should you use?
Use OKLCH for new design tokens, themes, and any color math. It’s the only one of the three where lightness is perceptually uniform, which means a 60% blue and a 60% red actually look like the same brightness to a human eye. That single property fixes the two oldest CSS color bugs: gradients that turn muddy in the middle, and dark-mode palettes where some colors look heavier than others at the same nominal lightness.
Use HSL when you’re tweaking an existing palette by hand and don’t want a context shift. It’s still readable, still in every browser, and most existing design systems are already speaking it. Don’t rewrite a working HSL token system just because OKLCH exists.
Use HEX as the export and copy-paste format. Designers paste HEX into Slack. Brand guidelines list HEX. Email templates only understand HEX. It’s the universal interchange format and will be for the foreseeable future — but it’s a poor format to think in, because nothing about #3B82F6 tells you whether it’s blue, how saturated it is, or how dark.
That covers about 90% of decisions. The rest of this post is the why, the example output, and the edge cases.
What HEX, HSL, and OKLCH actually look like
All three describe colors, but they encode wildly different mental models. Here’s the same blue (#3B82F6 — the Tailwind blue-500) in each.
HEX:
color: #3B82F6;
Six hex digits = three bytes = red, green, blue channels from 0 to 255. The format dates to 1996 (CSS1) and tells you nothing semantic. To know whether #3B82F6 is light or dark you have to mentally average the bytes, which nobody actually does.
HSL:
color: hsl(217 91% 60%);
Hue (0–360°), saturation (0–100%), lightness (0–100%). HSL was a huge readability win when CSS3 added it in 2011 because you can finally read a color: “217° is blue-ish, 91% saturation is vivid, 60% lightness is medium-bright.” The catch: HSL’s “lightness” is perceptual nonsense. hsl(60 100% 50%) (yellow) and hsl(240 100% 50%) (blue) both claim 50% lightness, but yellow looks dramatically brighter than blue to a human eye.
OKLCH:
color: oklch(62% 0.18 264);
Lightness (0–100%, perceptually uniform), chroma (0 to ~0.4, perceptual saturation), hue (0–360°). Defined by Björn Ottosson’s Oklab color space and added to CSS Color Level 4. Now 60% actually means “60% as bright as white” regardless of hue. A 60% yellow and a 60% blue look like the same brightness, which is the whole point.
This tiny semantic difference is the entire reason OKLCH exists.
Why OKLCH is the right default for design tokens in 2026
The pitch isn’t theoretical. Three concrete things break in HSL and HEX that just work in OKLCH:
1. Gradients don’t go muddy. A linear-gradient(to right, hsl(0 100% 50%), hsl(240 100% 50%)) (red → blue) interpolates through a desaturated grey-purple in the middle, because HSL interpolates lightness as raw RGB averaging. The same gradient in OKLCH (linear-gradient(in oklch, oklch(63% 0.26 29), oklch(45% 0.31 264))) stays vivid the whole way. You can see this in any modern Chrome or Firefox build — paste both into DevTools side by side and the OKLCH version is visibly cleaner.
2. Dark-mode palettes stop fighting you. A common bug: you generate a dark theme by inverting lightness in HSL, and your blues end up clearly heavier than your yellows even though every token is at the same lightness. In OKLCH, mapping oklch(L C H) → oklch(100% - L C H) actually preserves perceived contrast because L is perceptually uniform. Stripe, GitHub, and Radix all switched their token systems to OKLCH-ish foundations between 2023 and 2025 for exactly this reason.
3. Accessibility math gets simpler. WCAG contrast is roughly a function of perceived lightness difference. With OKLCH, two tokens at oklch(95% ...) and oklch(35% ...) will pass AA against each other regardless of hue. With HSL you have to keep recomputing — hsl(60 100% 50%) on white fails AA, but hsl(240 100% 50%) on white passes it, because perceptually they’re nothing alike.
If you’re writing new design tokens — or auditing an existing system — OKLCH is the format that makes the math match what your eyes already see.
When HSL is still the right answer
OKLCH does not make HSL obsolete. HSL is still the right tool when:
- You’re nudging an existing color, not designing a system. “Make this button 5% darker on hover” is a one-line HSL tweak (
hsl(217 91% 55%)) that any frontend dev can read at a glance. The same edit in OKLCH (oklch(58% 0.18 264)) requires either tool support or muscle memory you haven’t built yet. - Your team’s existing tokens are HSL. Migrating a working token system has a real cost — every component, every Storybook, every brand guideline. If your HSL tokens are working, leave them. Adopt OKLCH for new themes, dark-mode pairs, or accent palettes.
- You’re in a non-CSS context. SVG
fill,<canvas>2D contexts, and many design tools still don’t accept OKLCH. HSL converts cleanly to RGB without any out-of-gamut handling.
A practical pattern: keep HSL where it already lives, but write any new token in OKLCH. The two formats coexist in the same stylesheet without issue.
When HEX is fine — and when it actively misleads
HEX is the universal copy-paste format. Designers paste it. Slack auto-previews it. Email clients only understand it. Don’t waste energy trying to evict HEX from your handoff workflow.
Where HEX actively misleads is anywhere you have to reason about the color. “Make #3B82F6 10% darker for the hover state” is impossible to do by eye — you have to convert to HSL or OKLCH, adjust, and convert back. That’s two round trips through a color converter for a one-line CSS change.
The modern pattern: write your tokens in OKLCH (or HSL), and let the build system or a CSS variable expose HEX only at the leaves where it has to interop with something that doesn’t speak modern color.
OKLCH vs HSL vs HEX: decision matrix
| Use case | HEX | HSL | OKLCH |
|---|---|---|---|
| New design token system | Avoid — semantically opaque | Fine, working pattern | Recommended. Perceptually uniform, future-proof |
| Dark-mode palette derivation | Avoid | Workable but uneven | Recommended. Lightness inversion just works |
| Tweaking one color by hand | Awkward | Recommended. Most readable | Fine, requires familiarity |
| Gradients without muddy middles | No | No (interpolation is RGB-based) | Recommended. Use in oklch |
| Accessibility / WCAG contrast | Hard to reason about | Misleading per-hue | Recommended. L roughly tracks perceived lightness |
| Brand guideline export | Recommended. Universal | Fine | Avoid — most tools still don’t accept it |
| Email-template inline styles | Required. No alternative | Limited support | No — not supported in email clients |
SVG fill / <canvas> | Recommended. Always works | Works | Spotty — convert to HEX at the leaf |
| Wide-gamut display-p3 colors | No | No (sRGB only) | Recommended. OKLCH is gamut-aware |
color-mix() and relative-color math | Works | Works | Recommended. Best-behaved interpolation |
If the row in your project has “recommended” in the OKLCH column, that’s almost always the modern pick. If it has “required” in the HEX column, don’t fight it.
Browser support and the fallback question
OKLCH, color-mix(), and relative color syntax are all Baseline 2024 — meaning Chrome 111+, Safari 16.4+, Firefox 113+, and every evergreen browser since. In 2026 that covers roughly 96% of global traffic, depending on which analytics dashboard you trust. Realistically, you can ship OKLCH directly in production CSS today without a polyfill.
If you have to support older browsers, the standard pattern is the CSS cascade itself:
.button {
/* Fallback for browsers that don't understand oklch() */
color: #3B82F6;
/* Modern browsers will use this */
color: oklch(62% 0.18 264);
}
No @supports query needed — browsers that don’t understand oklch() simply ignore the second declaration and use the first. This is the same fallback pattern that’s been used for rgba(), hsl(), and every other color format since CSS3.
Common pitfalls when adopting OKLCH
A few things that trip teams switching to OKLCH:
- Out-of-gamut chroma. OKLCH’s chroma channel goes beyond what sRGB can render.
oklch(70% 0.4 30)is a real color in OKLCH but unrenderable on a standard sRGB display, so the browser clamps it to the gamut boundary. This is a feature, not a bug — but if you generate tokens by mechanical LCH math, you’ll occasionally produce a value that renders differently than expected. Most converters (including the Kestrel Tools Color Converter) flag out-of-gamut colors so you can adjust before committing. - Treating chroma like HSL saturation. HSL saturation is 0–100%; OKLCH chroma is 0 to ~0.4 with no fixed maximum. Picking
chroma: 100because that’s what saturation looks like in HSL produces wildly out-of-gamut colors. Start around0.1–0.2for vivid sRGB colors. - Forgetting to specify the interpolation space in gradients.
linear-gradient(red, blue)still interpolates in sRGB by default, even if your endpoints are written as OKLCH. You have to explicitly saylinear-gradient(in oklch, ...)to get the perceptually-uniform interpolation that’s the whole point. - Mixing OKLCH and HSL tokens in the same component. Pick one per component or per theme layer. Mixing makes it impossible to reason about whether two tokens are perceptually equivalent.
So which one should you write in 2026?
The defaults have shifted. Reach for OKLCH when designing tokens, themes, and anywhere you do color math. Keep HSL for quick by-hand tweaks and any existing token system that’s already working. Keep HEX as the export format for designer handoff, email, and brand guidelines — but stop thinking in HEX, because nothing about six hex digits is meant to be parsed by a human.
If you want to see the difference, take any HSL or HEX brand color and convert it to OKLCH. Look at the chroma value: that’s the perceptual saturation your old format was hiding from you. Bump it up or down by 0.02 and watch how subtly the color shifts versus how aggressively the same change in HSL saturation would have shifted it. That gentleness — small numeric changes producing small visible changes — is the entire reason designers started writing in OKLCH in the first place.
You can try this side-by-side in the browser right now with the Kestrel Tools Color Converter — paste any HEX or HSL value and see the OKLCH equivalent, plus an in-gamut warning if your color falls outside sRGB. It runs entirely client-side, so the brand palette you’re auditing never leaves your machine. If you’ve been defaulting to HSL since 2014, this is the smallest possible experiment to feel what OKLCH actually changes.