OKLCH to HEX Converter_
Convert oklch to hex as you type, or drag the three sliders. Lightness takes a percentage or a number from 0 to 1, chroma takes either scale, hue takes any of the four angle units, and an alpha after the slash becomes the fourth hex pair.
The reason this needs a real tool is that most OKLCH colours have no hex. The space is bigger than any screen, so a well-formed value like oklch(70% 0.37 150) simply is not a colour sRGB can show. This page brings it back the way CSS Colour Level 4 specifies — reducing chroma and holding the hue — and shows you what plain channel clipping would have given you instead.
This colour does not fit, so it has to be brought back. Two ways of doing that, side by side — the hue figure is how far each answer sits from the hue you asked for.
- Input
- An oklch() value: lightness, chroma and hue, with optional alpha after a slash. Sliders drive the same conversion.
- Output
- The hex code, the gamut it needed, the chroma ceiling at that lightness and hue, and progressive CSS with the hex as a fallback.
- Processing
- CSS Colour Level 4 §13.2 gamut mapping, run in this tab: a binary search on chroma with deltaEOK deciding when to stop.
- Limits
- Hex is sRGB by definition, so a wide-gamut colour cannot survive the trip intact. What the tool can do is choose where the loss goes.
- Mapping against clipping
- Measured over 1,512 out-of-gamut colours: mapping moves the hue 2.91° on average, clipping 5.68°. The worst clipping case in that sweep shifts hue by 24.8° — an orange arriving as a red.
Most OKLCH colours have no hex
The space is bigger than the screen
OKLCH describes colour perceptually rather than by what a monitor can emit, so it can name colours no monitor emits. oklch(70% 0.37 150) is valid, unambiguous, and not shown by any display you own. Convert it naively and the arithmetic produces linear-light values below zero or above one — the colour is off the edge of the box. Something has to give before it can be written as six hex digits, and the interesting question is what.
Clipping is the obvious answer and it is wrong
Clamp each channel into 0–1 and you get a colour, quickly, in one line. But the three channels are clamped by different amounts, and moving them unevenly rotates the hue. Take oklch(55% 0.214 70), an orange just past the sRGB boundary: clipping returns #bb4d00, which sits 24.8° away in hue — a red, not an orange. Gamut mapping returns #a35f00, 6.1° away, which still reads as the colour that was asked for.
What the spec does instead
CSS Colour Level 4 §13.2 binary-searches the chroma downward, holding lightness and hue, until the colour sits within a just-noticeable difference of the boundary — then clips that. The give lands almost entirely in saturation. Across 1,512 colours past the sRGB edge this page's tests measure a mean hue drift of 2.91° against clipping's 5.68°, and it wins on 69% of them. It is not magic: it loses on about 2%, and its own worst case is 15.7°. The page reports the actual figure for whatever you convert rather than asking you to take the average on trust.
Why trade distance for hue
Here is the part that surprises people, and the tests measure it too: by raw perceptual distance the two strategies are a tie. Mean deltaEOK across that same sweep is 0.0565 mapped against 0.0548 clipped — clipping is fractionally closer. It still looks worse, because the eye forgives a colour that is slightly too dull far more readily than one that has changed hue. The spec spends its error where it will not be noticed, which is a judgement about perception rather than a mathematical optimum.
Convert one colour, or find the edge of the space
- 01Paste an
oklch()value, or type the three numbers bare. Lightness accepts 70% or 0.7, chroma accepts 0.15 or 37.5%, and the hue takes any CSS angle unit. - 02Watch the chroma slider label: it shows the ceiling at your current lightness and hue, so you can sit just inside the gamut instead of discovering the edge by accident.
- 03Read the badges. In sRGB means the hex is exact. Needs P3 means a modern screen shows more than the hex can carry. Outside both means no display renders it as written.
- 04Copy the progressive CSS when the colour is wide-gamut — the hex declaration first, the oklch() after it, so old browsers stop at the fallback and new ones take the better value.
Four colours on their way out of OKLCH
A design token that has to ship as hex
The system is authored in OKLCH; a config file only takes hex.
oklch(50% 0.2 300)
accent: "#773ac1"
A vivid green that sRGB cannot hold
Inside Display P3, outside sRGB — so ship both.
oklch(70% 0.25 145)
color: #00c30b; color: oklch(70% 0.25 145);
An orange that clipping would turn red
The same value, handled two ways, 18.7° of hue apart.
#a35f00 — 6.1° drift
#bb4d00 — 24.8° drift
Finding the edge before building a scale
How much chroma is available at this lightness and hue?
sRGB ceiling 0.220
ceiling 0.299
What each component means, and what it accepts
| Component | Range | Accepted spellings | What it controls |
|---|---|---|---|
| Lightness | 0 to 1, or 0% to 100% | 70%, 0.7, none | Perceived lightness — unlike HSL, holding it constant across hues really does hold brightness constant |
| Chroma | 0 to about 0.4 in practice | 0.15, 37.5% (100% means 0.4) | Colourfulness, unbounded in the spec but limited by what a screen can emit |
| Hue | 0 to 360 | 240, 240deg, 0.5turn, 4.19rad, 200grad | Position on the hue circle — the component gamut mapping protects |
| Alpha | 0 to 1, or a percentage | / 0.5, / 50% | Becomes the fourth hex pair; has no effect on whether a colour exists |
A missing component written as none is treated as zero here, which is what a plain conversion needs; in animations and relative colour syntax none carries a different meaning.
How far each gamut reaches at a few lightnesses
| Lightness | Hue | sRGB chroma ceiling | Display P3 ceiling |
|---|---|---|---|
| 50% | 0 (red) | 0.203 | 0.228 |
| 70% | 145 (green) | 0.220 | 0.299 |
| 90% | 90 (yellow) | 0.128 | 0.157 |
| 30% | 264 (blue) | 0.174 | 0.206 |
| 62.8% | 29.2 (the sRGB red primary) | 0.258 | 0.288 |
| 0% and 100% | any | 0 | 0 |
Display P3 reaches further at every lightness and hue tested, which is the whole reason for shipping a progressive declaration. At the two extremes of lightness both gamuts close to a point — there is no such thing as a saturated black.
Working with a space that is bigger than the screen
- Build scales by holding lightness and stepping hue — that is the property OKLCH has and HSL does not, and it is why the space is worth using at all.
- Check the chroma ceiling before committing to a palette value. Sitting at 90% of it gives you a colour that survives on every screen without being mapped.
- Ship the hex first and the
oklch()second in the same rule. Browsers take the last declaration they understand, so this needs no feature query. - Do not round an OKLCH value to four decimals and assume it still fits — the sample above shows sRGB red rounded that way landing just outside its own gamut.
- A chroma of 0 makes hue meaningless: every hue gives the same grey, which is why this page reports the hue of a grey as 0 rather than whatever the arithmetic produced.
- If a colour needs P3 and the design cannot tolerate a fallback that differs, lower the chroma until the badge says sRGB rather than shipping two different-looking colours.
Where an OKLCH conversion loses something
Hex is sRGB, so wide-gamut colour cannot survive it
Six hex digits mean sRGB by definition. A colour that needed Display P3 arrives as the nearest sRGB colour and stays there. The conversion is not reversible: converting the hex back gives you the mapped colour, not the one you asked for. Keep the oklch() value as the source of truth and treat the hex as a build artefact.
Not every tool maps the same way
Browsers, design tools and other converters implement gamut mapping to varying degrees, and some just clip. The same oklch() value can therefore produce different hex codes in different places. This page states which strategy it used and shows the alternative, so a mismatch elsewhere is at least explicable.
Mapping is better on average, not always
Across the sweep in the test suite, clipping produces the closer hue on about 2% of out-of-gamut colours, and mapping’s own worst case in that sweep is a 15.7° shift. The claim here is that mapping wins on the whole and loses gracefully, not that it is exact.
Chroma has no fixed maximum
The spec does not cap chroma, so oklch(70% 5 150) parses. Nothing renders it, and the mapping will pull it back a very long way. A value far past 0.4 usually means a unit mistake — a percentage written as a number, or a value copied from a different colour space.
Lightness without a percent sign runs 0 to 1
oklch(70 0.15 240) is not 70% — it is out of range, and browsers treat it as clamped to white. This page rejects it with a message naming the likely intent rather than silently returning white.
Algorithms, matrices and tolerances
- Accepted input
- oklch() with commas or spaces, or three bare numbers; lightness as a percentage or 0–1; chroma as a number or a percentage of 0.4; any of the four CSS angle units on the hue; alpha after a slash; none for any component
- Colour maths
- Björn Ottosson’s OKLab matrices in both directions, the sRGB transfer function applied signed so out-of-gamut channels stay measurable, and a D65 primaries change for Display P3
- Gamut mapping
- CSS Colour Level 4 §13.2 as written: binary search on chroma with a just-noticeable difference of 0.02 and a search tolerance of 0.0001, deltaEOK measured as Euclidean distance in OKLab
- Gamut test tolerance
- 1e-5 on the encoded channel. Round-tripping through two cube roots costs about 1.4e-7 in linear light, and the transfer function’s slope of 12.92 near zero multiplies that — at 1e-6 the sRGB green primary tested as outside sRGB, which it defines
- Round-tripping
- Every hex has an exact OKLCH; a sweep of 4,096 hex values across the cube returns each one unchanged. The loss runs the other way, since OKLCH holds colours hex cannot
- Chroma ceiling
- Binary search on the gamut boundary at fixed lightness and hue, verified in the tests to be inside the gamut and outside it 0.002 further out
- Scope
- Building palettes and checking contrast belong elsewhere; the HSL to HEX converter covers why HSL lightness misleads, and ordinary rgb(), hsl() and alpha notation live on the RGB to HEX converter
- Network
- None from tool code. A test sweep calls every function this page uses with
fetchandXMLHttpRequestreplaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.
Questions about OKLCH, hex and gamut
How do I convert OKLCH to hex?
Convert OKLCH to OKLab, then to linear-light sRGB through Ottosson’s matrices, apply the sRGB transfer function, and pack the three results into six hex digits. The step everyone skips is what to do when they fall outside 0–1, which happens for a large share of OKLCH values. This page handles that with the CSS Colour 4 algorithm rather than by clamping.
Why does my OKLCH colour come out duller than expected?
Because it was outside the gamut and the chroma had to come down. The tool shows the requested chroma and the delivered one, plus the ceiling at that lightness and hue. If the badge says Needs P3, the colour exists on modern screens and only the hex fallback is dull — ship the progressive CSS and both audiences get the best available.
What is gamut mapping, and why not just clamp the channels?
Clamping moves the three channels by different amounts, which rotates the hue: an orange past the sRGB edge clips to a red. Gamut mapping reduces chroma while holding hue and lightness, so the colour stays recognisably itself. Measured over 1,512 out-of-gamut colours here, mapping drifts 2.91° of hue on average against clipping’s 5.68°, with a worst case of 24.8° for clipping.
Can I convert hex back to OKLCH?
Yes, and that direction is exact — every hex has one OKLCH value. The round trip is only lossless if the colour was inside sRGB to begin with. A wide-gamut OKLCH converted to hex and back returns the mapped colour, not the original, which is why the oklch() value should stay the source of truth.
Is OKLCH supported in browsers?
Yes, in every current engine. The practical question is not support but gamut: a P3-capable screen shows more than an sRGB one, so shipping the hex as a fallback declaration followed by the oklch() value gives each browser the best colour it can render, with no feature query needed.
What is the difference between OKLCH and OKLab?
They are the same space in different coordinates. OKLab uses two opponent axes, a and b; OKLCH converts those to a chroma and a hue angle, which is far easier to reason about — you can hold a hue and vary saturation. This page takes oklch() and says so if you paste oklab().
Do the colours I convert leave my browser?
The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.