
There is a specific kind of frustration that every designer knows. You spend forty minutes landing on exactly the right blue — the one that communicates calm without tipping into cold, that works against the off-white background without going grey, that holds its weight at small sizes without going muddy. You export it. You hand it off. And then you see it on someone else's screen and it is just slightly, undeniably wrong.
Not wrong in a way you can explain easily. Wrong in the way that makes you wonder if you are losing your mind or your eye. Wrong in the way the client's projector makes your carefully calibrated teal look like hospital scrubs. Wrong in the way your brand red looks warm and confident on your MacBook and slightly aggressive on the Windows machine in the conference room.
The problem is not your eye. The problem is the coordinate system you are using to describe color. And once you understand what HEX codes actually encode — and what they completely ignore — the reason every cross-device color hand-off has a small chance of going wrong becomes obvious.
What a HEX Code Actually Says
A HEX color code is a base-16 (hexadecimal) representation of three integers, each ranging from 0 to 255, that instruct a display device how much voltage to send to the red, green, and blue subpixels at a given location. Nothing more.
#FF0000 breaks down as: FF = 255 in decimal, meaning maximum drive signal to red subpixels. 00 = 0, meaning no signal to green subpixels. 00 = 0, meaning no signal to blue subpixels.
It is a hardware instruction. The statement it makes is: Dear display, please drive your red subpixels at full power and leave the others off. That is the complete, total semantic content of a HEX color code.
What the human looking at that display actually sees is determined by something else entirely — the physical characteristics of the display's subpixels, its white point, its gamma curve, its peak luminance, its panel type, the ambient lighting in the room, and the visual system of the specific person doing the looking.
The Three Things HEX Does Not Encode
Absolute luminance. #FFFFFF on a dim office projector produces roughly 300 candelas per square meter (nits) of white. The same #FFFFFF on an iPhone 16 Pro in outdoor brightness mode produces 2,000 nits. The HEX is identical; the actual light output differs by 6.6x. Any color expressed as a HEX value will appear lighter or darker depending on the display's maximum brightness — and there is no way to express that in the HEX code itself.
The color of the primaries. Every display has red, green, and blue subpixels, but red is not a universal standard. A red OLED subpixel and a red LCD backlit subpixel produce different wavelength distributions. #FF0000 on a wide-gamut P3 display produces a genuinely different color — a more saturated, slightly different hue — than #FF0000 on a standard sRGB display. The code is the same; the photons are different.
The gamma and white point. The relationship between the numeric value sent to a display and the luminance it outputs is nonlinear, governed by the gamma curve. sRGB uses an approximately 2.2 gamma. Some older displays shipped with 1.8 gamma. The white point — the color of maximum white — is nominally D65 (6500K) for sRGB but varies by panel calibration. Both of these affect how every intermediate color value is rendered.
The Same HEX on Five Real Screens
To make this concrete: what does #E8532A — a warm, mid-saturation orange — look like across real production hardware?
| Display | Panel Type | Color Gamut | Peak Luminance | Perceived Result |
|---|---|---|---|---|
| MacBook Pro 16 M4 | Liquid Retina XDR | DCI-P3 wide | 1,000 nits (SDR: 500) | Warm, vivid, slightly punchy orange |
| Dell UltraSharp U2723D | IPS LCD | sRGB 100% | 400 nits | Accurate, slightly flatter orange |
| Samsung Galaxy S25 Ultra | AMOLED | DCI-P3 wide | 2,600 peak | Noticeably more saturated, punchier |
| Budget 1080p office monitor | TN LCD | sRGB 72% | 250 nits | Noticeably muted, slightly brownish |
| Epson EB-L series projector | 3LCD | sRGB 85% | 300 nits reflected | Desaturated, cooler, looks like terracotta |
| Apple Vision Pro | Micro-OLED | P3 wide | 5,000 nits peak | Rich and highly saturated — most vivid rendering |
Six devices. One HEX code. Six perceptually different colors. None of them is wrong — each device is faithfully executing the instruction #E8532A within its own hardware constraints. The HEX code is just a blind coordinate: it points at a location in an abstract three-dimensional cube and trusts that every device's cube is the same shape. They are not.
Why Human Vision Makes This Worse
The display-hardware problem would be bad enough on its own. But human perception adds another layer of complexity that pure hardware calibration cannot solve.
Simultaneous Contrast: The Enemy of Flat Color Thinking
Colors do not exist in isolation for human vision. The same color patch appears lighter on a dark background and darker on a light background — an effect called simultaneous lightness contrast. Two identical HEX greys placed on black and white backgrounds will be perceived as different shades even though the encoded values are identical.
Hue contrast works the same way. A neutral grey surrounded by a warm background shifts slightly cool. The same grey surrounded by a cool background shifts slightly warm. The HEX value #808080 has not changed. The visual system has.
This matters for UI design because the color you sample from a palette or spec sheet is never evaluated in isolation — it is always evaluated against adjacent colors, backgrounds, and the ambient environment. A brand blue that tests perfectly against white in isolation may read differently against the dark card backgrounds in a dark-mode layout using the same HEX value.
The Perceptual Non-Uniformity of RGB
Here is the deeper structural problem with using RGB (and therefore HEX) for color design decisions: equal numeric steps in RGB do not correspond to equal perceived color differences.
Consider stepping from #000000 to #FF0000 in 5% increments. The perceptual jump between the first increment (near-black to very dark red) is enormous — your eye immediately sees the difference. The perceptual jump between #F01010 and #FF0000 — numerically the same step size — is nearly invisible. The RGB cube is perceptually compressed in some regions and stretched in others.
Practically: if you increase a color's brightness value by 30 HEX units, you have no way of knowing from the RGB coordinates whether that 30-unit change produces a barely noticeable shift or a dramatic one. You have to look. The coordinates are not telling you.
This is the core problem that a class of color spaces called perceptually uniform color spaces was invented to solve.
CIE LAB: The First Attempt at a Human Coordinate System
In 1976, the International Commission on Illumination (CIE) published a color space designed around human visual perception rather than display hardware. CIE LAB — usually written as L*a*b* or simply Lab — has three axes that attempt to directly encode how humans perceive color differences rather than what signals a display receives.
What L*, a*, b* Actually Mean
L* — Lightness. Runs from 0 (perceptual black) to 100 (perceptual white). Critically, L* is derived from the cube root of the physical luminance ratio, which approximately compensates for the nonlinear response of human vision to light. Equal steps in L* are approximately equal perceived lightness differences.
a* — The green-red axis. Negative values are green; positive values are red. An a* of +60 is a vivid red; an a* of -40 is a vivid green; an a* of 0 is neither.
b* — The blue-yellow axis. Negative values are blue; positive values are yellow. Same structure as a*.
The coordinate (L*=50, a*=+60, b*=+40) describes a color completely independently of any display device. It does not say give the display 255 red and 80 green. It says the color that a human observer with standard vision would perceive as medium-lightness, distinctly red-shifted, somewhat yellowish. Any display can be calibrated to reproduce this perceptual target — and the LAB coordinate is what specifies the target.
The conversion from sRGB HEX to LAB goes through an intermediate step via a device-independent absolute color space called CIE XYZ, which anchors everything to physical radiometric measurements. The full chain: HEX to linear RGB to XYZ to LAB.
Delta-E: Measuring Color Difference in Human Units
Once you have colors in LAB, you can calculate Delta-E — the Euclidean distance between two LAB coordinates — which measures color difference in approximately perceptual units.
| Delta-E Value | What a Human Observer Perceives |
|---|---|
| 0.0 to 1.0 | Imperceptible — indistinguishable under normal viewing |
| 1.0 to 2.0 | Perceptible only to a trained eye under controlled conditions |
| 2.0 to 3.5 | Noticeable on direct comparison; acceptable for most print |
| 3.5 to 5.0 | Clearly visible difference; problematic for brand consistency |
| 5.0 to 10.0 | Obvious to any observer — distinctly different colors |
| 10.0 and above | Completely different colors by perception |
This is what HEX cannot give you. The numeric distance between #FF0000 and #FE0000 — one unit in RGB — has no predictable perceptual meaning. In some color regions, a 1-unit change is invisible. In others, it is the difference between two identifiably different hues. Delta-E in LAB space gives you a number that actually maps to human perception. That is the fundamental upgrade.
Where LAB Still Falls Short
LAB was a major advance, but it was designed in 1976 and calibrated against human perception data from that era. Modern research has revealed two persistent weaknesses.
Hue linearity. The a* and b* axes are not perfectly linear for hue at high chroma (vivid saturation). Blues in LAB in particular have a well-documented hue-rotation problem: as you increase saturation along the b* axis, the perceived hue shifts toward purple rather than staying at a clean blue. Designers working with vivid blues or purples in LAB have to account for this manually.
Chroma uniformity. Equal steps in chroma (how saturated a color is) do not produce perfectly equal perceived saturation differences across all hue angles. Greens and yellows are compressed compared to reds and blues in ways that require correction formulas. Delta-E 2000, for instance, adds three weighting functions to compensate for LAB's chroma non-uniformity.
These limitations are why, in 2020, a new color space emerged that addressed them.
OKLCH: The Color Space Built for 2026 Screens
OKLCH is a polar reformulation of the Oklab color space published by Bjorn Ottosson in 2020. It was specifically designed to fix the hue-linearity and chroma-uniformity problems in LAB while maintaining the device-independence that makes LAB useful. CSS Color Level 4 — now supported in all major browsers — includes OKLCH as a first-class color value. It is not a future technology. It is available right now.
What OKLCH Fixes That LAB Did Not
OKLCH uses three coordinates: L (Lightness, from 0 to 1), C (Chroma or saturation, from 0 to roughly 0.37 for sRGB colors), and H (Hue angle, from 0 to 360 degrees).
L — Same concept as LAB's L*, but with improved uniformity. Equal L steps produce more consistently equal perceived brightness steps, particularly in the dark and saturated regions where LAB struggles.
C — Zero is fully achromatic (grey). Higher values are more saturated. Crucially, OKLCH's chroma axis is more perceptually uniform than LAB's — equal C steps produce more equal perceived saturation steps across all hues.
H — Hue angle in degrees. Red sits around 29 degrees, Yellow around 109, Green around 142, Cyan around 194, Blue around 264, Purple around 308. The hue angle rotates through the color wheel in a way that is much more perceptually linear than LAB's a* and b* Cartesian axes.
The blue hue-rotation problem that plagues LAB is largely solved in OKLCH. A vivid blue at C=0.20 and H=264 stays perceptually blue as you adjust C — it does not slide toward purple the way LAB blues do.
OKLCH in Actual CSS
OKLCH is the first color space where you can adjust individual perceptual properties — lightness, saturation, hue — in isolation without the other properties drifting. The table below shows a systematic brand palette built in OKLCH, and identifies exactly which single coordinate changes each variant from the base.
| CSS Token | OKLCH Value | What Changes vs. Base |
|---|---|---|
| --brand-blue | oklch(0.55 0.18 264) | Base value — medium blue |
| --brand-blue-tint | oklch(0.78 0.18 264) | L raised to 0.78 only — lighter, hue and chroma locked |
| --brand-blue-shade | oklch(0.35 0.18 264) | L lowered to 0.35 only — darker, hue and chroma locked |
| --brand-blue-muted | oklch(0.55 0.07 264) | C reduced to 0.07 only — desaturated, lightness and hue locked |
| --brand-blue-complement | oklch(0.55 0.18 84) | H rotated 180 degrees — complementary orange, L and C locked |
Now try doing the equivalent operations starting from #2B6CB0. To lighten it, you cannot simply add a fixed offset to each channel — the channels interact nonlinearly and the perceived hue will drift as you do it. You have to convert to HSL, adjust L, and convert back — and even HSL is not perceptually uniform, so you cannot predict how much the lightness will actually appear to change.
The OKLCH workflow makes the relationship between the coordinate and the perception explicit and stable.
The Display Gamut Problem: P3, Rec.2020, and Beyond
Device-independence in LAB and OKLCH solves the perception problem. But there is a second, parallel problem that HEX papers over: gamut — the range of colors a display can physically reproduce.
What Gamut Means and Why It Breaks HEX
A display's gamut is the set of all colors it can produce by mixing its red, green, and blue primaries. Because each display has different primary colors (different peak wavelengths from its LEDs or OLEDs), each display's gamut is a different-shaped region of the visible color spectrum.
The sRGB gamut — designed in 1996 for CRT monitors — covers approximately 35% of the colors visible to the human eye (the CIE 1976 UCS color space). DCI-P3, used in modern iPhones, MacBooks, and cinema monitors, covers approximately 54%. Rec.2020, the HDR television standard, covers approximately 75%. The human eye, under optimal conditions, can resolve colors that none of these standards reach.
A HEX color written for sRGB — #FF0000, pure sRGB red — maps to a specific point in the visible spectrum. When that code is sent to a P3-capable display running in P3 mode, the display can reproduce colors beyond what the HEX number was designed for. Depending on how the browser and OS handle color management, #FF0000 on a P3 display may be rendered as sRGB red (correctly contained within sRGB), or it may attempt to use the display's full P3 red primary — producing a noticeably more vivid red than the designer intended.
Gamut Coverage by Display Type
| Display Category | Color Standard | Gamut vs. sRGB | Approx. % Visible Spectrum |
|---|---|---|---|
| Budget TN/VA monitors | sRGB partial | 72 to 92% of sRGB | 25 to 32% |
| Standard IPS monitors | sRGB | 99 to 100% of sRGB | ~35% |
| MacBook Pro, iPhone 16, iPad Pro | DCI-P3 | ~133% of sRGB | ~54% |
| Samsung Galaxy S25, Pixel 9 Pro | DCI-P3 / Display P3 | ~130% of sRGB | 50 to 54% |
| Reference monitor (Eizo, NEC) | AdobeRGB | ~130% of sRGB | ~50% |
| HDR TV (Samsung QD-OLED) | Rec.2020 partial | 175 to 190% of sRGB | 65 to 70% |
| Apple Vision Pro | DCI-P3 | ~133% of sRGB | ~54% |
| Theoretical full HDR display | Rec.2020 full | ~220% of sRGB | ~75% |
The budget monitor at 72% of sRGB coverage and the MacBook Pro at 133% sRGB coverage sit at opposite ends of a 1.85x range — nearly double the color range. A vivid orange you designed on a P3 display may physically not exist on a 72% sRGB budget monitor. The display cannot reproduce it. It will map that HEX to the nearest color it can show, which is the muted, slightly different version the client on the budget screen sees.
LAB and OKLCH are gamut-independent by definition — they describe perceptual targets that may or may not be within any specific display's gamut. A color described in OKLCH with a high chroma value may be out-of-gamut for sRGB but perfectly renderable on P3. The designer can see this before hand-off, check the gamut boundary, and make an intentional decision — rather than discovering the gamut clip on the client's screen.
The Conversion Chain: From HEX to LAB to OKLCH
Understanding the conversion chain matters because it reveals where the assumptions live — and where cross-device color accuracy actually comes from.
| Step | From | To | What Happens |
|---|---|---|---|
| 1 | HEX (#RRGGBB) | sRGB (0 to 1 float) | Divide each channel by 255: #FF0000 becomes (1.0, 0.0, 0.0) |
| 2 | sRGB | Linear RGB | Reverse gamma: apply the sRGB piecewise transfer function inverse |
| 3 | Linear RGB | CIE XYZ (D65) | Multiply by a 3x3 matrix encoding the sRGB primary chromaticities |
| 4 | CIE XYZ | CIE LAB | Apply cube-root compression with D65 white point normalization |
| 5 | CIE LAB | Oklab | Apply a second linear matrix transform (Ottosson 2020) |
| 6 | Oklab | OKLCH | Convert Cartesian (a,b) to polar (C,H): C = sqrt(a2+b2), H = atan2(b,a) |
Step 2 — reversing gamma — is where most quick HEX-to-LAB converters make their first error. sRGB does not use a pure power-law gamma; it uses a piecewise function with a linear segment near black. Skipping the exact transfer function and approximating with a simple 2.2 power law introduces a measurable Delta-E error in dark colors. For design tools, this error is generally invisible. For print production matching, it can push a color past the Delta-E 2.0 perceptibility threshold.
Step 3 encodes the single most important assumption in the whole chain: the sRGB primaries. The XYZ values produced by this matrix are only physically meaningful if the source display is calibrated to sRGB. An uncalibrated monitor, an LCD with aged backlight, or a display with wider primaries will produce different XYZ values for the same HEX code — which is why hardware calibration is the prerequisite for accurate color management, not the afterthought.
Why You Still Ship HEX (But Design in OKLCH)
None of this is an argument for abandoning HEX in production. HEX is the current lingua franca of the web — it is compact, universally parseable, and supported everywhere. CSS browsers render it, design tools export it, developers paste it into code without a second thought.
The argument is about where in your workflow the color decisions are made. When you are choosing a palette, building a design system, testing whether two colors create sufficient contrast, verifying that a hover state is detectably different from its base, or checking that your dark-mode variants are perceptually consistent with their light-mode equivalents — those decisions should be made in OKLCH or LAB. The coordinates are telling you something real about what you and your users will actually see.
When you are shipping, convert to HEX or to CSS oklch() values directly (browsers handle the gamut mapping for you). The output format is an implementation detail. The color decisions are the substance.
The designer who specifies oklch(0.55 0.18 264) and ships #2B6CB0 is making the same perceptual decision with explicit awareness of what the number means. The designer who picks #2B6CB0 directly is making the same decision blindly — trusting that their screen, their colleague's screen, the client's browser, and the production environment all agree on what that instruction produces. Increasingly, they do not.
Practical Workflow for Cross-Device Color
The workflow shift is smaller than it sounds. It does not require rebuilding your design system or retraining your team on color science. It requires three targeted changes.
Audit your palette in OKLCH or LAB before locking it. Convert your existing brand colors using a color converter and examine the L* (lightness) values. If your light background and dark text have L* values of 85 and 45, the contrast is predictable and robust across displays. If your high-contrast button pair has a Delta-E of only 18, it may not be as legible as it appears on your calibrated monitor.
Generate systematic scales in OKLCH, not HSL. HSL lightness is not perceptual — stepping from HSL L=40 to L=60 produces a visibly different lightness jump in blue than in yellow, because the underlying color space is not perceptually uniform. OKLCH L steps are consistent across hues. A design system token scale generated in OKLCH will have visually even steps; the same scale generated in HSL will have visible irregularities at certain hues.
Check gamut boundaries before hand-off. Convert your most vivid colors (high-saturation brand colors, bright accents) to OKLCH and check their chroma values. Colors above approximately C=0.25 are likely outside sRGB gamut — meaning they will be gamut-clipped on any display not running in P3 or wider mode. Know which colors are at risk before your client sees them on a standard monitor.
The coordinate system you use to describe color determines whether you are designing blindly or deliberately. HEX is the language of display instructions. LAB and OKLCH are the language of human perception. The translation between them is not lossy — it is clarifying. The color was always going to look like something on a real screen; understanding the conversion just means you get to decide what it looks like rather than being surprised by it.
Written by Devansh Gondaliya, MERN Stack Developer & AI Systems Engineer. Devansh writes about the intersection of design systems and engineering — the specifications, conversions, and coordinate systems that determine what actually ships to the user's screen.




