Skip to main content
October 1, 2026 · colors · 9 min read

OKLCH vs Lab vs HSL — Which Color Space When

HSL for one-off tweaks, OKLCH for ramps, Lab for print color match. A plain-English walkthrough of when each pays off.

Last updated October 1, 2026 · 9 min read

The 60-second answer:

  • HSL — fast mental model, use for tweaking a single color by eye.
  • OKLCH — perceptually uniform, use for ramps, interpolation, and anything programmatic.
  • Lab (and LCh) — the color-science grandparent, use for print color matching and legacy pipelines.
  • sRGB / hex — storage and transport. Not a working space, a wire format.

The longer answer is that each of these solves a different problem, and the mistake teams make is picking one for all four jobs. HSL is wrong for ramps; OKLCH is overkill for a one-time darken; Lab is painful for anything a non-color-scientist has to think about. Match the tool to the task.

HSL — the fast one

Hue, Saturation, Lightness. hsl(221, 83%, 53%). Everyone who has ever used a color picker understands it: slide hue around the wheel, slide saturation left to grey, slide lightness up to white.

What HSL is great for:

  • Mental conversion from a hex. #2563EB → "that's a saturated-ish mid-blue around 220°." Fast.
  • Tweaking a single color. "Make it 10% darker" with hsl(221, 83%, 43%) is instantly readable.
  • CSS-in-JS at-the-moment adjustments. hsl(var(--brand-h), 83%, calc(53% - 10%)) composes legibly.

What HSL is bad for:

  • Shade ramps. hsl(h, s, 90%) and hsl(h, s, 10%) have wildly different perceived lightness distances from hsl(h, s, 50%) depending on the hue. A yellow at L=50% looks much lighter than a blue at L=50%. Your "even ramp" won't be even.
  • Interpolation across hues. HSL interpolates straight across hue, which cuts through grey around the complement. hsl(0, 90%, 50%) → hsl(180, 90%, 50%) passes through mud.
  • Chroma consistency. HSL's S=100% means "as saturated as sRGB can go at this lightness," which for a dark red is way less chromatic than for a light yellow. Numbers that look identical paint very different colors.

Day-to-day shop: if your task is "I have a hex, I want to nudge it," HSL Palette Explorer is the fastest hands-on tool to feel what each axis does.

Lab — the one you learned about and then stopped using

CIE Lab (L\a\b\) was designed to be perceptually uniform in a way sRGB isn't. L\ is lightness (0 = black, 100 = white). a runs green–red. b runs blue–yellow. LCh is the polar cousin — L\* lightness, C chroma, h hue.

It is genuinely perceptually better than HSL. The problem is nobody wants to think in a/b coordinates. "Nudge this a bit more red" is a += 10, which is fine for a script and awful for a human.

Where Lab still earns its keep:

  • Delta-E color difference. ΔE is a Lab-space number. If you need to assert "these two colors are visually indistinguishable" (ΔE < 1) or "customers will see these as the same red" (ΔE < 2.3 is the just-noticeable threshold), Lab is the arithmetic.
  • Print-to-screen matching. Pantone and most print pipelines ship Lab values. Converting sRGB → Lab → Pantone-nearest is standard.
  • Older design tools. Photoshop's "Lab color" mode exists because it's useful for sharpening on L without touching chroma. Still true in 2026.

If you don't live in print or color science, you probably never touch Lab directly. You touch OKLCH, which is Lab's friendlier descendant.

For scenarios where you need print-aware palettes with Pantone references, the Pantone-Inspired Color Name Generator returns swatches with names and categorisation that map cleanly onto Pantone families.

OKLCH — the one most new tools default to

OKLCH is Björn Ottosson's 2020 "Oklab," reparameterised as Lightness, Chroma, hue. The pitch: Lab's perceptual uniformity, HSL's readability.

An OKLCH value looks like oklch(0.68 0.19 44). L is 0–1 (not 0–100 like Lab). Chroma is open-ended but usefully bounded around 0–0.4 for in-gamut sRGB. Hue is 0–360°.

Why it has eaten HSL for design-system work:

1. Ramp stepping is linear and perceptual. oklch(0.95 c h), oklch(0.85 c h) ... oklch(0.15 c h) produces visually even steps across any hue. HSL doesn't. 2. Chroma is independent of lightness. You can hold chroma constant across a ramp and the shift feels smooth, instead of HSL's "the midtones are the only ones that look saturated." 3. Hue interpolation stays in color space. Blending red to cyan in OKLCH goes through pink and purple, not grey. 4. CSS supports it natively. color: oklch(0.68 0.19 44) works in all modern browsers (since early 2023 in Chromium, same in Safari and Firefox).

What OKLCH is bad for:

  • Mental model from a hex. No one looks at oklch(0.546 0.216 262.9) and thinks "that's Tailwind blue-500." You read it in a converter.
  • Out-of-gamut math. OKLCH can describe colors sRGB can't. Clipping behavior varies between tools. If you're generating ramps you'll run into "oklch(0.85 0.3 25) can't be displayed on a sRGB monitor" and your tool will have an opinion about how to gracefully degrade.
  • Non-digital output. Nothing in print speaks OKLCH natively yet.

The one-sentence rule of thumb: if you're writing a --token, write it in OKLCH. If you're painting one pixel by hand, use whatever picker your tool offers.

sRGB and hex — storage, not thinking

#2563EB is three bytes. It's compact, parseable, universally supported. It is also perceptually meaningless — #808080 and #000000 are 128 apart on each channel but perceptually one is "medium" and the other is "black."

Treat hex the way you treat timestamps. You store them. You transport them. You convert to a human-friendly working space (OKLCH, Lab, even HSL) before reasoning about them. The one exception: brand references and legal documents. "Our red is #C8102E" is a specification. OKLCH is for the design system to produce accurate ramps from that spec.

For converting an RGB breakdown to the right working space while you prototype, the Random Color with RGB & HSL Values Generator is a quick sanity-check tool — it emits one color in every notation so you can see the same color three ways at once.

When to use which, by task

Making a brand ramp. OKLCH. Lock hue, step lightness, taper chroma. Convert to hex at the end. HSL ramps look uneven; the fix is not "more effort in HSL," the fix is a different color space.

Picking a complement for an existing hex. HSL. Rotate hue 180°, keep S and L. It'll be approximately right. If you need it exactly right, convert to OKLCH, add 180° to hue.

Interpolating between two colors for a gradient. OKLCH, or the browser's native color-mix(in oklch, ...). HSL interpolation goes through grey when the two hues are far apart.

Converting a print spec to screen. Lab. The print spec is in Lab (or Pantone, which maps to Lab). Compute ΔE between your screen approximation and the target.

Darkening a hex by 10% for a hover state. HSL. Fast, legible, close enough. OKLCH is better in principle, overkill for one state.

Building a 11-stop neutral grey ramp. OKLCH, chroma = 0.01, hue = something near your brand. HSL will give you cold-feeling middles and warm-feeling edges or vice versa.

Testing accessibility. None of these — WCAG uses relative luminance, computed from sRGB. Converting to OKLCH does not change the contrast number. For actual auditing use the Color Contrast Pair Generator which reports the WCAG ratio directly.

The one gotcha everyone hits

OKLCH chroma is not the same number as HSL saturation. If you convert hsl(220, 83%, 50%) to OKLCH you get something like oklch(0.52 0.19 263). Chroma 0.19 is near the top of sRGB's capacity. Saturation 83% is "not quite max." The scales don't align.

Practical fallout: when a designer says "same saturation, different hue," they probably want constant OKLCH chroma, not constant HSL saturation. Constant HSL saturation across hues produces visibly different levels of vividness.

The one book-pattern worth copying

The approach Radix UI formalised (and most modern systems have copied):

1. Hue is a lock. Each color scale is one hue family. 2. Lightness is a 12-step ramp (they use 1–12 rather than 50–950, but the idea is the same). 3. Chroma is tuned per hue to stay in gamut and reach the same perceived vividness as the other hues at the same step. 4. The 9th and 10th steps are always the "solid" color (button backgrounds, etc.) because that's what reads as the brand regardless of hue.

This is OKLCH thinking even when the published values are hex. If you want to produce it from scratch, the Hue-Based Color Palette Generator is a decent starting point — generate the raw hue, then build your ramp in OKLCH on top of it.

When "wrong" color space is fine

Don't over-optimize. A marketing landing page with four hex codes doesn't need an OKLCH pipeline. The sign your color space choice is actually biting:

  • Your ramp has a visible flat spot in the middle. (HSL ramp through yellow / cyan.)
  • Your gradients grey out in the middle. (HSL interpolation.)
  • Your dark-mode brand feels undersaturated compared to light-mode. (HSL S value at low L.)
  • Your palette looks fine on your monitor and wrong on the designer's. (Gamut clipping, not color space per se.)

If none of those bite, HSL is fine. The reason you switch to OKLCH is to make specific problems go away, not because it's newer.

The practical starter kit

For a team adding OKLCH to an existing stack this week:

1. Keep hex as the storage format. 2. Add a build-time converter that reads your tokens, outputs OKLCH coordinates for every one, and writes them to a .generated.css your app imports. 3. Author new ramps in OKLCH directly. Old ramps keep working. 4. When a ramp needs a tweak, re-author in OKLCH, let the converter write hex. 5. Keep one HSL tool bookmarked for individual-color eyeballing — HSL Color Palette Generator does the job.

Nobody has to rewrite the whole system. The payoff is that new work gets the perceptual benefits, and the old work keeps functioning until it gets its own OKLCH re-tuning on the next design-debt sprint.

A few worked conversions

Just to anchor the math, here are three Tailwind-family colors across all four spaces, so you can see how the same color reads differently depending on how you describe it.

Blue-500 — #3B82F6

  • HSL: hsl(217 91% 60%) — reads as "pretty saturated blue, bright."
  • Lab: L=59, a=10, b=-61 — the strongly negative b says "very blue."
  • OKLCH: oklch(0.63 0.19 253) — hue 253°, chroma 0.19 (near sRGB max for that lightness).
  • Takeaway: a mid-high saturation blue with lightness around 0.63 OKLCH. Will need a darker shade for AA on white.

Red-500 — #EF4444

  • HSL: hsl(0 84% 60%) — "saturated red, bright."
  • Lab: L=58, a=65, b=42 — large positive a (red) with warm bias (positive b*).
  • OKLCH: oklch(0.63 0.22 29) — slightly higher chroma than the blue because red can go further at this lightness.
  • Takeaway: same OKLCH L as the blue, visually similar perceived brightness. HSL numbers don't tell you that.

Green-500 — #22C55E

  • HSL: hsl(142 71% 45%) — "mid-green, slightly dark."
  • Lab: L=71, a=-67, b=47 — strongly negative a (green) with warm bias.
  • OKLCH: oklch(0.73 0.17 149) — notably higher lightness than the blue/red at same HSL "lightness."
  • Takeaway: Green at HSL L=45% is perceptually much lighter than Blue at HSL L=60%. OKLCH surfaces this; HSL hides it.

Running the three through the RGB Color Palette Generator and comparing the OKLCH-tagged outputs makes the "same HSL, different OKLCH" relationship visible in seconds — faster than doing the conversion by hand.