Reading L, C and H Next to R, G and B
R, G and B are instructions to a screen: how strongly to light each of its three sub-pixels, from 0 to 255. L, C and H describe the colour you see as a result:
- L, lightness, from 0% to 100%, follows how light the colour looks rather than how much light is emitted.
- C, chroma, is how colourful it is; 0 is a grey, and any colour with R, G and B all equal has chroma 0.
- H, hue, is the angle in degrees that says which colour family it belongs to.
The default colour here, rgb(67, 97, 238), shows the difference. Its blue channel is nearly full, yet it converts to oklch(55.58% 0.21411 269.02): barely above mid-lightness, strongly coloured, with a hue in the blue range.
OKLCH vs RGB: When Each Is Useful
rgb() is the format graphics code speaks. Canvas pixel data, image files, video, most charting libraries and every colour picker work in red, green and blue, and an rgb() value says exactly what a standard screen should display. Keep it wherever colours are measured, sampled or handed to code.
OKLCH is the better format for deciding what a colour should be:
- Perceptual lightness. In OKLCH, matching L means matching apparent lightness, so a set of coloured labels or chart series can share one L and look equally prominent. Equal RGB totals do nothing of the kind.
- Predictable palettes. A theme can keep one hue and chroma and vary only lightness for backgrounds, borders and text, or rotate H by a fixed step to get related accents.
- Wide gamut. rgb() is limited to sRGB, but oklch() can name the more saturated colours of Display P3 screens. On a standard screen the browser brings them back into range.
For background on how RGB, HSL and LAB relate, see the colour spaces guide.
From rgb() to oklch() in CSS
The modern rgb() and oklch() syntaxes look alike: three space-separated values and an optional alpha after a slash. Converting a set of design tokens is a one-for-one swap:
/* before */
--accent: rgb(67 97 238);
--accent-soft: rgb(67 97 238 / 15%);
/* after */
--accent: oklch(55.58% 0.21411 269.02);
--accent-soft: oklch(55.58% 0.21411 269.02 / 15%);Once a token is in OKLCH, related shades come from changing one number. For a dark theme, raise the lightness and keep the hue:
@media (prefers-color-scheme: dark) {
:root { --accent: oklch(72% 0.14 269.02); }
}Lighter colours cannot hold as much chroma in sRGB: at 72% lightness this hue fits at most about 0.145, which is why the example lowers C to 0.14. If a new value lands outside sRGB, the OKLCH to RGB converter shows its gamut-mapped and clipped sRGB values.
Browser Support
oklch() works in Chrome and Edge 111+, Safari 15.4+ and Firefox 113+. Where you need to support older browsers too, keep the rgb() value as a fallback, or test for support explicitly:
.badge { background: rgb(67 97 238); }
@supports (color: oklch(0% 0 0)) {
.badge { background: oklch(55.58% 0.21411 269.02); }
}The @supports block is handy when a whole group of declarations should switch together, such as a palette whose steps were tuned in OKLCH.
RGB to OKLCH Reference Values
White, black, the three primaries and this site's brand purple, converted at build time.
| Colour | rgb() | oklch() | L | C | H |
|---|---|---|---|---|---|
| White | rgb(255, 255, 255) | oklch(100% 0 0) | 100% | 0 | 0 |
| Black | rgb(0, 0, 0) | oklch(0% 0 0) | 0% | 0 | 0 |
| Red | rgb(255, 0, 0) | oklch(62.8% 0.25768 29.23) | 62.8% | 0.25768 | 29.23 |
| Green | rgb(0, 255, 0) | oklch(86.64% 0.29483 142.5) | 86.64% | 0.29483 | 142.5 |
| Blue | rgb(0, 0, 255) | oklch(45.2% 0.31321 264.05) | 45.2% | 0.31321 | 264.05 |
| Brand purple | rgb(196, 78, 237) | oklch(64.57% 0.23988 316.07) | 64.57% | 0.23988 | 316.07 |
White and black have chroma 0 and no hue. Of the primaries, blue has the most chroma, 0.31321, and green the most lightness, 86.64%.
How We Calculate
Channel values are whole numbers from 0 to 255; a decimal is rounded and anything outside the range is clamped, as the note under each field says. Each channel is divided by 255, decoded from the sRGB gamma curve (D65 white point) to linear light, and converted to OKLab with Björn Ottosson's published OKLab matrices. Chroma and hue are the polar form of OKLab's a and b axes, as defined for oklch() in CSS Color 4.
The result is printed with lightness as a percentage to 2 decimals, chroma to 5 decimals and hue to 2. At that precision every 8-bit RGB colour converts back to exactly the same channel values. Every RGB colour is inside sRGB, so this direction never needs gamut mapping.
The Pantone panel ranks inks with the weighted RGB comparison our Pantone converters use, and its matches are approximate.