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.

toolkit.codes/oklch-to-hex
Enter an oklch() colour to convert it
UTF-8
Ready
100% LOCAL
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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Token
oklch(50% 0.2 300)
Config
accent: "#773ac1"

A vivid green that sRGB cannot hold

Inside Display P3, outside sRGB — so ship both.

Input
oklch(70% 0.25 145)
CSS
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.

Mapped
#a35f00 — 6.1° drift
Clipped
#bb4d00 — 24.8° drift

Finding the edge before building a scale

How much chroma is available at this lightness and hue?

At L 70%, H 145
sRGB ceiling 0.220
Display P3
ceiling 0.299

What each component means, and what it accepts

ComponentRangeAccepted spellingsWhat it controls
Lightness0 to 1, or 0% to 100%70%, 0.7, nonePerceived lightness — unlike HSL, holding it constant across hues really does hold brightness constant
Chroma0 to about 0.4 in practice0.15, 37.5% (100% means 0.4)Colourfulness, unbounded in the spec but limited by what a screen can emit
Hue0 to 360240, 240deg, 0.5turn, 4.19rad, 200gradPosition on the hue circle — the component gamut mapping protects
Alpha0 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

LightnessHuesRGB chroma ceilingDisplay P3 ceiling
50%0 (red)0.2030.228
70%145 (green)0.2200.299
90%90 (yellow)0.1280.157
30%264 (blue)0.1740.206
62.8%29.2 (the sRGB red primary)0.2580.288
0% and 100%any00

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 fetch and XMLHttpRequest replaced 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.