Perceptually uniform colors with oklch
HSL looks like it should be perceptually uniform, but it isn't. Yellow at hsl(60 100% 50%) appears far brighter than blue at hsl(240 100% 50%) at the same lightness. oklch uses a model where L actually means the same perceived brightness across all hues.
2--brand: oklch(0.55 0.2 264);
3--brand-light: oklch(0.75 0.2 264);
4--brand-dark: oklch(0.35 0.2 264);
5/* only L changes */
2--yellow: hsl(60 100% 50%); /* blinding */
3--blue: hsl(240 100% 50%); /* dark */
4/* manually tweak each shade to look balanced */
5--brand-light: hsl(246 87% 68%);
Oklch Browser Support
This feature is well established and works across many devices and browser versions. It has been available across browsers since 2023.
Safe to use without fallbacks.
Predictable lightness
L: 0.5 looks the same perceived brightness in oklch regardless of the hue. That's not true in HSL.
Easy palette generation
Change only L for lighter or darker shades. Change only H to shift hue. Change only C for saturation. They're independent.
Wide gamut ready
oklch can express P3 colors that hex and sRGB can't. On wide-gamut displays the chroma can go higher than sRGB allows.
How it works
HSL was supposed to be human-friendly but its lightness channel is not perceptually uniform. Yellow at hsl(60 100% 50%) looks far brighter than blue at hsl(240 100% 50%) even though both have L: 50%. Building a consistent color palette means manually adjusting each shade by eye until it looks balanced.
oklch(L C H) uses a perceptually uniform lightness model. L: 0.55 looks the same perceived brightness whether the hue is green, orange, or purple. To create a lighter shade, increase L. To shift hue, change H. Chroma C controls saturation. All three values are genuinely independent in a way HSL's S and L are not.