Lesson 16 of 30
HSL's lightness channel lies to you — the same lightness number produces different perceived brightness depending on hue. oklch() fixes that, and color-mix() gives you native color blending without a preprocessor.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
Why is oklch() generally a better choice than hsl() for generating a programmatic color scale (e.g. 10 shades of a brand color)?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
hsl() describes a color as hue, saturation, and lightness, and for years it was the readable alternative to hex codes. The problem is that HSL's lightness channel isn't perceptually uniform. Ask for a yellow at 50% lightness and a blue at 50% lightness, and the yellow will look noticeably brighter to the human eye than the blue does, even though the number is identical. HSL was designed around how color is encoded, not around how it's actually seen.
oklch() is a newer color function built on a perceptually uniform color space (OKLab). It also takes three main values — lightness, chroma (roughly: how saturated), and hue — but its lightness channel is calibrated so that "the same lightness number" really does mean "the same perceived brightness," regardless of hue. That single property is what makes it genuinely useful for design-token work, not just a syntax swap.
color-mix() is a separate function that blends two colors together natively in CSS: color-mix(in oklch, red 40%, blue) mixes 40% red with 60% blue, computed in whatever color space you name. No Sass, no JS color library, no precomputed hex value — the browser does the math.
Imagine building a 10-step scale for a brand color the old way, in HSL, by hand-tuning lightness at each step:
/* HSL: same lightness step, uneven perceived brightness */
--brand-100: hsl(230 80% 90%);
--brand-500: hsl(230 80% 50%);
--brand-900: hsl(230 80% 10%);
That looks systematic, but change the hue to a brand yellow (hsl(50 80% 50%)) and 50% lightness renders dramatically lighter than the blue did at the same number — the scale's steps stop feeling even the moment the hue changes. Now the same idea in OKLCH:
:root {
--brand-l-100: 0.95;
--brand-l-500: 0.60;
--brand-l-900: 0.20;
--brand-c: 0.15;
--brand-h: 250;
--brand-100: oklch(var(--brand-l-100) var(--brand-c) var(--brand-h));
--brand-500: oklch(var(--brand-l-500) var(--brand-c) var(--brand-h));
--brand-900: oklch(var(--brand-l-900) var(--brand-c) var(--brand-h));
}
/* Native blending instead of a preprocessor mix() or a JS color library */
.button:hover {
background: color-mix(in oklch, var(--brand-500) 85%, white);
}
Because OKLCH's lightness is perceptually uniform, swapping --brand-h to generate a scale for a different brand color (say, a secondary accent) and reusing the same lightness steps produces a scale that steps evenly for that hue too — the token system stays consistent across colors instead of needing hand-tuned lightness values per hue, which is exactly the bug HSL-based scales run into in practice.
color-mix() percentages describe the first color's share, and they don't have to sum to 100%. color-mix(in oklch, red 40%, blue) implies blue takes the remaining 60% if you omit its percentage, but omitted-vs-explicit rounding rules are a real source of subtle mismatches — write both percentages explicitly when it matters.color value position broadly — they work in background, border-color, box-shadow, anywhere a color is expected, not just as standalone declarations.hsl(). Treat oklch()/color-mix() as safe to reach for in new work but verify with @supports (color: oklch(0.5 0.1 200)) if you need a hard guarantee on older browser targets, and keep a plain hex/HSL fallback declared before the OKLCH one — CSS custom-property-style fallback isn't automatic here, so declare the safe value first and let the modern one override it where supported."Why would you pick OKLCH over HSL for a design system's color scale?" tests whether a candidate understands perceptual uniformity as a concrete, practical property, not just a trivia fact about a newer CSS function.