oklch — the newest addition to CSS color syntax — represents colors using perceptual lightness, chroma, and hue, and its key property is that changing the L value actually produces colors that appear equally light to the human eye, unlike HSL where "50% lightness" produces visually different lightness levels across different hues
The previous articles on this site covered color model basics, WCAG contrast ratios, design tokens and dark mode, color blindness and redundant signals, and color spaces and P3 wide gamut. This article addresses perceptual color spaces — why HSL's apparent simplicity conceals perceptual inconsistency, what oklch actually solves, and how to use it practically in CSS.
The HSL perceptual problem
HSL (Hue, Saturation, Lightness) was designed to be more intuitive than RGB for designers — instead of red/green/blue components, you specify the angle on a color wheel (hue), how vivid the color is (saturation), and how light it is (lightness).
The problem: HSL's "lightness" is not perceptually uniform. Consider:
- Yellow at HSL(60, 100%, 50%): appears very bright
- Blue at HSL(240, 100%, 50%): appears much darker
- Both are at "50% lightness" in HSL
Why they look different at the same HSL lightness: HSL's lightness is a mathematical average of RGB components — not a measure of how light the color appears to human eyes. Human visual perception is not equally sensitive to all wavelengths — we're most sensitive to green light, less sensitive to red, and least sensitive to blue. The same mathematical "brightness" value produces very different perceived brightness across hues.
The consequence for design systems: when building a palette by varying HSL lightness while keeping hue constant, each hue family produces colors at different perceived brightness at the same L value. A "40% lightness blue" and a "40% lightness yellow" look dramatically different to users.
What oklab and oklch solve: perceptual uniformity
oklab and oklch (developed by Björn Ottosson, published 2020) are perceptually uniform color spaces — meaning equal numeric differences produce equal perceived differences in color appearance.
oklch uses three channels:
- L (Lightness): 0 to 1, where 0 is black and 1 is white. Perceptually uniform — oklch L=0.5 produces the same apparent lightness regardless of hue.
- C (Chroma): 0 to approximately 0.4 in practice. Higher chroma = more vivid. 0 chroma = neutral grey at that lightness.
- H (Hue): 0° to 360°, the angle on the color wheel (similar to HSL hue but mapped to a different perceptual space).
The key difference from HSL: if you hold L and C constant while changing H in oklch, you get colors that appear equally light and equally vivid. In HSL, the same operation produces colors that look dramatically different in apparent brightness.
CSS oklch syntax: practical implementation
oklch is now supported in all modern browsers (Chrome, Safari, Firefox — since 2023):
/* oklch(lightness chroma hue) */
color: oklch(0.65 0.2 250); /* A blue-purple */
color: oklch(0.65 0.2 120); /* A green at identical perceived lightness */
color: oklch(0.65 0.2 30); /* An orange at identical perceived lightness */
Contrast ratio calculation note: WCAG 2.x contrast ratios use relative luminance calculated from sRGB — not from perceptual color spaces. An oklch color must still be converted to sRGB for WCAG compliance checking. The advantage of oklch isn't in WCAG compliance (that's still based on sRGB luminance) but in perceptual consistency when building design systems.
oklch for dark mode palettes: one of oklch's strongest practical use cases is building dark mode color palettes. In HSL, creating a "darker version" of a color for dark mode requires adjusting L differently for each hue to maintain visual consistency. In oklch, systematically reducing L by a fixed amount produces consistently darker-appearing colors across all hues.
Wide gamut colors and display-p3
Traditional web colors are defined in the sRGB color space — the color gamut (range of representable colors) of standard monitors from the 1990s. Modern displays, particularly Apple's retina displays and newer monitors, support P3 (Display P3), which has a ~25% wider gamut.
oklch can represent colors outside sRGB — its color space is large enough to specify the vivid reds, greens, and blues that P3 displays can show but sRGB cannot. On a P3-capable display:
/* A vivid green outside sRGB gamut — shows correctly on P3 displays */
color: oklch(0.7 0.3 145);
/* Fallback for non-P3 displays */
@supports not (color: oklch(0 0 0)) {
color: hsl(130, 70%, 45%);
}
Gamut clipping: on sRGB displays, oklch colors outside the sRGB gamut are "clipped" — mapped to the nearest sRGB color. The visual result is still usable but less vivid than on P3 displays.
CMYK vs RGB: print vs screen color models
CMYK (Cyan, Magenta, Yellow, Key/Black) is the color model used for printing. The distinction from screen color models:
RGB is additive: mixing red, green, and blue light. All colors at maximum = white. Used for screens (which emit light).
CMYK is subtractive: inks absorb specific wavelengths of reflected light. All inks at maximum = black. Used for printing (which reflects light).
Converting between CMYK and RGB is lossy — both color spaces have colors the other can't reproduce. Some vivid screen colors (bright neons, deep blues) are outside the CMYK gamut and print duller than they appear on screen. Some printing colors (certain greens and oranges) are outside sRGB but within CMYK — they print vibrantly but appear muted on screen.
For web design: CMYK values are irrelevant (browsers use RGB). If producing assets for both web and print, design in a color space large enough to contain both (Adobe RGB or ProPhoto RGB), then export separately for web (sRGB) and print (CMYK with a specific print profile).
How to use the Color Converter on sadiqbd.com
- oklch exploration: enter an oklch value to see the equivalent HEX/RGB for copying into CSS, or convert an existing HEX color to oklch to find its perceptual coordinates
- Palette consistency check: convert multiple brand colors to oklch — if the L values are very different across colors intended to appear at the same visual weight, this explains why they look unbalanced
- CMYK-to-web: when given CMYK values from a print designer, convert to HEX for web use — be aware the converted color is the closest sRGB approximation, which may look different from the printed output
Frequently Asked Questions
Should I use oklch for all my CSS colors right now? For new projects targeting modern browsers, yes — with fallbacks. For legacy projects, gradually. All major browsers have supported oklch since late 2023, and the progressive enhancement approach (provide HEX/RGB as fallback, oklch as enhancement) works well. The practical benefit is most visible in design systems where maintaining perceptual consistency across a large color palette is important — a single designer managing 100+ colors across multiple hues benefits significantly from oklch's perceptual uniformity. For simple projects with a few brand colors, the difference is less impactful.
Is the Color Converter free? Yes — completely free, no sign-up required.
Try the Color Converter free at sadiqbd.com — convert between HEX, RGB, HSL, HSV, oklch, and CMYK instantly with live preview.