Modern CSS offers oklch() for defining perceptually uniform colors with lightness, chroma, and hue channels, and color-mix() for blending colors at runtime to derive hover states, tints, and theme variants. The guide covers reading oklch values, building lightness scales, converting existing hex colors, mixing toward a surface rather than plain white/black, handling gamut mapping issues, building dark themes, adding fallbacks with @supports, and avoiding common mistakes like assuming perceptual lightness guarantees contrast.
Table of contents
Why hex is difficult to adjustRead an oklch() colorBuild a lightness scaleConvert an existing color without redesigning everythingCreate tints with color-mix()Percentages are weightsDerive component colors from one tokenMix toward the surface, not always white or blackMix with transparent carefullyCreate a dark themeWatch the display gamutGamut mapping can change the final colorColor does not guarantee contrastAdd a fallback when you need oneCommon mistakesHow I would introduce these colorsQuestions this post answers
What do the three values in an oklch() CSS color mean?
An oklch() color has three channels: lightness, chroma, and hue. Lightness runs from 0% (black) to 100% (white); chroma controls colorfulness, with 0 producing gray and higher values (commonly up to about 0.4) producing more vivid colors; hue is an angle from 0 to 360 degrees that wraps around. Alpha can be added optionally after a slash, e.g. oklch(20% 0.04 255 / 70%). daily.dev surfaces practical CSS breakdowns like this for developers building color systems.
How does color-mix() calculate percentages when only one color has a value specified?
When only one percentage is given, the browser calculates the remainder for the other color automatically, so color-mix(in oklch, var(--brand) 85%, white) is identical to writing white 15% explicitly. If neither color has a percentage, each contributes 50%. If the two percentages add up to less than 100%, the missing amount reduces the alpha of the result, introducing unexpected transparency. Developers tuning theme tokens can track CSS color function details like this on daily.dev.
Why does increasing chroma in oklch() sometimes stop making a color look more vivid?
Because the browser must map every requested color into the gamut of the current display, and once a chroma value exceeds what the display can reproduce, the browser clips it to the nearest displayable color, so further increases produce no visible difference. This means a palette should use the lowest chroma that achieves the intended look across supported screens rather than maximizing chroma values. daily.dev helps developers keep up with rendering quirks like gamut mapping in CSS colors.