OKLCH vs HSL vs HEX: Which CSS Color to Use

HEX, HSL, and OKLCH can all describe the exact same pixel. #3b82f6, hsl(217 91% 60%), and an equivalent oklch(...) value can paint identical blue. So the question is never "which one is correct" — it's "which one makes the job in front of you easy." The three formats optimize for three different jobs, and picking by job is the whole game.

The one-line decision rule

Use HEX when you are pasting a fixed brand color and never touching it again. Use HSL when you want to hand-tweak hue, saturation, or lightness by reading numbers. Use OKLCH when you are building a scale or theme and need colors that look evenly spaced and consistently bright, or when you want vivid colors beyond the old sRGB range. If you remember nothing else: HEX for copy-paste, HSL for tweaking, OKLCH for systems.

HEX: the universal clipboard format

HEX is just RGB written in base 16 — two digits each for red, green, and blue, optionally two more for alpha. It is the lingua franca of design tools, brand guidelines, and Slack messages. Its strength is also its weakness: #7c3aed tells you nothing about whether it is light, dark, warm, or cool. You cannot eyeball-edit it. When a designer hands you #7c3aed as "the brand purple," paste it and move on. When you need to derive ten shades from it, convert it first with a color converter and switch to a format built for math.

HSL: readable, but it lies about brightness

HSL (hue, saturation, lightness) is far friendlier to humans than HEX. Hue is an angle on the color wheel (0 = red, 120 = green, 240 = blue), saturation is how vivid, and lightness runs 0% black to 100% white. Want a slightly darker button on hover? Drop the lightness a few points. Want a tint? Raise it. You can author and adjust HSL by hand, which is why it dominated CSS theming for a decade.

The trap is that HSL lightness is not perceptual brightness. The "L" is computed from RGB math, not from how the human eye actually senses light. So equal L values do not look equally bright across different hues.

The worked example: why HSL lightness lies

Compare two colors that HSL claims are identically light:

hsl(60 100% 50%)    /* yellow */
hsl(240 100% 50%)   /* blue   */

Both are set to 50% lightness. Drop them side by side and the yellow looks glaringly bright while the blue looks dark and heavy. They are nowhere near the same perceived brightness, even though the number says they should be. This is not a rendering bug — it is a consequence of HSL deriving lightness from raw RGB rather than from human vision. The eye is far more sensitive to green and yellow wavelengths than to blue, and HSL ignores that entirely.

The practical fallout: if you build a color scale by stepping HSL lightness in even increments — 90%, 80%, 70%, and so on — the steps will not look evenly spaced. Some jumps look tiny, others look huge, and the same lightness step produces a usable mid-tone in one hue and a muddy mess in another. Designers end up hand-correcting every swatch.

OKLCH: lightness that matches your eyes

OKLCH uses the same three intuitive components — Lightness, Chroma, Hue — but builds them on the Oklab color space, which is designed so that equal numeric distances correspond to equal perceived differences. According to the MDN documentation for oklch(), the lightness axis runs 0 (black) to 1 (or 0% to 100%), chroma is the amount of color, and hue is the angle.

Set our two colors to the same OKLCH lightness and they actually look equally bright:

oklch(0.7 0.2 100)   /* yellow-ish */
oklch(0.7 0.2 264)   /* blue-ish   */

Because the lightness is perceptual, an OKLCH scale built by stepping L evenly produces steps that look evenly spaced to a human. Change the hue while keeping L and C fixed and the brightness stays put — which is exactly the property you want for a theming system where every accent color must hold the same visual weight. This is why OKLCH has become a recommended format for generating consistent design-system palettes and accessible theme ramps. If you are building an accessible theme, generate the ramp in OKLCH and then validate every pair against contrast targets with a WCAG palette checker; for ad-hoc swatch sets a color palette tool covers the basics.

OKLCH also unlocks wider color (P3)

HEX and the legacy hsl() notation are tied to the sRGB gamut — the limited range of colors most screens have shown for decades. OKLCH is not gamut-locked; it can express colors in the wider Display P3 gamut that modern phones and laptops can actually render, producing more vivid greens and reds than sRGB allows. Per the W3C CSS Color Module Level 4 specification, which defines oklch(), browsers clamp out-of-gamut values to what the display supports, so you get the most saturated color the hardware can show.

The caveat: browser support

OKLCH is the newest of the three. It is defined in CSS Color 4 and is supported in current versions of all major browsers — Chrome, Edge, Safari, and Firefox have shipped it since 2022-2023 — but it is not as universally safe as HEX or HSL on very old engines. If you must support legacy browsers, the practical pattern is to author in OKLCH for the maintainable source of truth and provide a HEX or sRGB fallback declaration immediately before it — older browsers ignore the rule they cannot parse and fall back to the prior one. Check the support tables for your target audience before shipping OKLCH as the only declaration. HEX remains the zero-risk default for a single static brand color.

Putting it together

A pragmatic workflow: keep brand constants as HEX because that is how everyone shares them. Reach for HSL when you are eyeballing a one-off hover state. Build any real color system — light/dark themes, accent ramps, status colors — in OKLCH so the scale is perceptually even and theme-friendly, then drop a HEX fallback for old browsers. When you need to move a value between formats, the safest move is to convert rather than re-eyeball: a color converter turns one notation into the others without changing the pixel. For more on choosing colors that pass accessibility checks, see our guide on color contrast and WCAG AA vs AAA.

Frequently Asked Questions

Not universally — it is better for a specific job. OKLCH wins for building perceptually even color scales, accessible themes, and accessing wider P3 colors. HEX is still best for copy-pasting a single fixed brand color, and HSL is fine for quick hand-tweaks. Pick by task, not by which is newest.

HSL derives lightness from raw RGB math, not human vision. The eye is far more sensitive to yellow and green than to blue, so hsl(60 100% 50%) yellow looks much brighter than hsl(240 100% 50%) blue even though both say 50%. OKLCH fixes this by using a perceptual lightness axis built on the Oklab color space.

OKLCH is defined in the W3C CSS Color 4 spec and is supported in all current major browsers (Chrome, Edge, Safari, and Firefox have shipped it since 2022-2023). It is not safe on very old engines, so for legacy support, declare a HEX or sRGB fallback immediately before the OKLCH rule. Older browsers ignore the value they cannot parse.

Yes. HEX, HSL, and OKLCH can all describe the identical pixel, so converting between them does not alter the displayed color (within the same gamut). Use a color converter to translate a value rather than re-guessing it by eye, which avoids drift between your source notations.

Both use a lightness axis, but HSL's is computed from RGB and does not match perception, while OKLCH's is built on Oklab so equal numeric steps look equally different to the eye. That is why even OKLCH lightness steps produce visually even scales and even HSL steps do not.