OKLCH Picker / Converter
Pick perceptual OKLCH colors and watch the sRGB and Display-P3 gamut boundaries instead of discovering the clip later — entirely on this device.
Max chroma at this lightness and hue: 0.192 in sRGB · 0.243 in Display-P3
OKLCH sets no maximum for chroma. This tool stops at 0.5, which contains every color Display-P3 can show.
Outside sRGB · Outside Display-P3
Both swatches are painted by your browser on this display. A color your screen cannot show is drawn as close as the screen gets, so trust the badge and the numbers over your eyes.
- Outside the sRGB gamut — the hex, rgb() and hsl() rows show the mapped color, not the one you asked for.
- Outside the Display-P3 gamut — the color(display-p3 …) row shows the mapped color, not the one you asked for.
- oklch()
oklch(0.7 0.4 30) - hex
#ff5843 - rgb()
rgb(255 88 67) - hsl()
hsl(7 100% 63%) - color(display-p3 …)
color(display-p3 1 0.285 0.193)
Fallback pair
color: #ff5843;
color: oklch(0.7 0.4 30);41 bytes · 2 lines
Two declarations, in this order: browsers without oklch() keep the hex, browsers with it take the second line. The hex is the mapped color, so the two lines are not the same color when the badge says you are outside sRGB.
Do it offline:$ node -e "const c=require('culori');console.log(c.formatHex(c.toGamut('rgb','oklch')(c.parse('oklch(0.7 0.4 30)'))))"
How to use it
- The page opens on oklch(0.7 0.4 30), a red no sRGB display can reach, so the out-of-gamut badge is visible immediately.
- Drag the L, C and H sliders, or paste a hex, rgb(), hsl(), oklab() or oklch() value to start from a color you have.
- Read the two badges separately — sRGB and Display-P3 are different targets — and read the chroma hint for the largest chroma each one holds here.
- Copy one notation row, or take the fallback pair and paste both declarations in order.
Set lightness, chroma and hue, and watch the gamut boundary move with them. This page prints how much chroma sRGB and Display-P3 hold at the lightness and hue you picked, badges each boundary on its own, and hands you the mapped hex fallback beside the oklch() line. Nothing leaves this tab.
How it works
Lightness runs 0 to 1 and hue is an angle, but chroma has no fixed ceiling: how much a target holds depends on the other two axes. At lightness 0.7 and hue 30, sRGB stops at 0.192 while Display-P3 reaches 0.243. The hint prints both, located by halving the interval 0 to 0.5 twenty-eight times.
Ask for more chroma than a target holds and the color must move. The procedure CSS Color Level 4 defines walks chroma down in OKLCH until the coordinates fit, then clips the remainder, moving lightness and hue slightly. Both boundary tests allow one part in a million: the OKLab round trip leaves float dust, and white reads as 1.0000000000000002.
The copy block holds two declarations: a browser that cannot parse oklch() drops that line and keeps the previous one, so order matters — hex first, oklch() second.
Examples
oklch(0.7 0.4 30)note Outside the sRGB gamut — the hex, rgb() and hsl() rows show the mapped color, not the one you asked for.
note Outside the Display-P3 gamut — the color(display-p3 …) row shows the mapped color, not the one you asked for.
oklch oklch(0.7 0.4 30)
hex #ff5843
rgb rgb(255 88 67)
hsl hsl(7 100% 63%)
display-p3 color(display-p3 1 0.285 0.193)
fallback color: #ff5843;
color: oklch(0.7 0.4 30);Chroma 0.4 is beyond both targets, so four rows describe a color moved to fit. Only the oklch row holds what was asked for, and the two fallback declarations differ.
#3b82f6oklch oklch(0.623 0.188 259.8)
hex #3b82f6
rgb rgb(59 130 246)
hsl hsl(217 91% 60%)
display-p3 color(display-p3 0.305 0.503 0.934)
max chroma 0.204 in sRGB · 0.219 in Display-P3No badge, no row moved: chroma 0.188 leaves 0.016 of sRGB headroom. Past 0.204 the hex row starts disagreeing with the oklch row.
oklch(0.7 0.22 30)note Outside the sRGB gamut — the hex, rgb() and hsl() rows show the mapped color, not the one you asked for.
oklch oklch(0.7 0.22 30)
hex #ff5843
rgb rgb(255 88 67)
hsl hsl(7 100% 63%)
display-p3 color(display-p3 0.968 0.392 0.303)
max chroma 0.192 in sRGB · 0.243 in Display-P3One badge, not two: 0.22 sits above the sRGB boundary of 0.192 and below the P3 boundary of 0.243, so only the hex row is a substitute.
oklch(70% 25% 30 / 50%)oklch oklch(0.7 0.1 30 / 0.5)
hex #d5867980
rgb rgb(213 134 121 / 0.5)
hsl hsl(9 53% 66% / 0.5)
display-p3 color(display-p3 0.793 0.541 0.488 / 0.5)Percentages resolve against the reference ranges: 70% lightness is 0.7, and 25% chroma is 0.1 because 100% means 0.4. Alpha as 50% or 0.5 lands the same.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Hue wraps, and stops mattering at chroma 0
A hue of -30 reads as 330: angles are taken modulo 360. At chroma 0 hue has no effect — oklch(0.5 0 30) and oklch(0.5 0 200) are one gray. A gray arriving as #888 has no hue at all, so it prints 0.
Chroma has no spec maximum; 0.5 is ours
OKLCH sets no upper bound, so the slider needed a limit chosen rather than quoted. Above 0.5 the value is clamped and the chip names the ceiling as ours. Negative chroma clamps to 0, as CSS does.
A none channel reads as 0
oklch(0.7 none 30) becomes chroma 0 with a chip saying so — the value a none channel computes to outside an interpolation. A chroma or hue that is not a real number, like 1e999, is refused rather than zeroed; in the lightness slot CSS clamps 1e999 to 1 first.
The hex fallback does not round-trip
The fallback of oklch(0.7 0.4 30) is #ff5843, which reads back as oklch(0.683 0.206 30.3): mapping discarded chroma and 8-bit channels rounded the rest. Keep the oklch() line authoritative.
The boundary vanishes at both lightness ends
Lightness 0 and 1 are black and white whatever chroma and hue say, so the boundary collapses. At hue 30 the sRGB hint reads 0.005 at lightness 0 and 0.000 at lightness 1, from a measured 0.0000004; at hue 0, lightness 0 reads 0.010.
Common errors
`blurple` is not a valid color — expected hex (3/4/6/8 digits), rgb(), hsl(), oklch(), or a CSS color name.- The field accepts hex in 3, 4, 6 or 8 digits, rgb(), hsl(), oklab(), oklch(), transparent and the CSS color names. Uppercase OKLCH(...) is fine; unclosed parens refuse.
Channel `c` is Infinity, not a finite number — lightness, chroma, hue and alpha must all be real numbers.- oklch(0.5 1e999 30) parses as far as an infinite chroma the matrices cannot use. Coercing it to 0 would show a gray and call it yours.
Chroma 0.9 is outside the 0–0.5 range this tool works in — clamped to 0.5. OKLCH itself sets no maximum chroma; this ceiling is ours.- A chip on a success, not a refusal: the report continues at chroma 0.5 and the message names the limit as ours.
Color is 257 characters — larger than the 256-character limit for a single color.- One color literal never needs 256 characters, so anything longer is refused, not truncated.
FAQ
Why doesn't the hex fallback match the color I chose?
Two losses stack: chroma outside sRGB is reduced until it fits, then the survivor is rounded into 8-bit channels. oklch(0.7 0.4 30) falls back to #ff5843, which reads back as oklch(0.683 0.206 30.3).
How much chroma can I use at a given lightness and hue?
Whatever the hint prints — it is measured, not estimated. At lightness 0.7 and hue 30, sRGB holds 0.192 and Display-P3 holds 0.243; at lightness 0.5 and hue 200, sRGB holds only 0.085.
Can a color sit outside sRGB but inside Display-P3?
Yes, and that band is why the badges are separate. oklch(0.7 0.22 30) exceeds the sRGB boundary of 0.192 but stays under the P3 boundary of 0.243, so only the hex, rgb() and hsl() rows move.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Apply the pasted color, or re-run the current values.
- ⌘/Ctrl + C
- Copy the focused row when no text is selected; a selection copies itself.
- ⌘/Ctrl + Shift + C
- Copy the focused row despite a text selection.
- ⌘/Ctrl + S
- Download the CSS snippet (a browser save-dialog override).
- t
- Step to the next theme palette; inert in a text field.
- ? or ⌘/Ctrl + /
- Open the help sheet listing these six bindings.