oklch-pickerPlaygroundGitHub

Colour utilities

The maths is framework-free and exported separately, useful for validating stored colours on a server, generating palettes, or naming colours in a table. No DOM, no UI.

import { colourName, maxChroma, clampToGamut, toOklch } from "@oklch-picker/core";

colourName("oklch(0.43 0.19 338)");  // "Dark pink"
maxChroma(0.7, 255);                 // 0.160, highest chroma sRGB can show there
clampToGamut({ l: 0.75, c: 0.35, h: 145 });  // chroma reduced until it fits

What is exported

FunctionDoes
toOklch, parseOklch, formatOklchParse and format
hexToOklch, oklchToHexConvert, exact round-trip within sRGB
parseRgb, formatRgb, oklchToRgb255The same for rgb() and rgba()
parseHsl, formatHslThe same for hsl() and hsla()
parseHwb, formatHwbThe same for hwb()
inGamut, clampToGamut, maxChromaGamut queries
gamutCurveCross-section data behind the charts
colourName"Dark pink", "Muted teal", "Light grey"
isLightWCAG luminance, for readable text over a swatch
addRecentThe recents list rule: dedupe to front, capped

Gamuts

SRGB is in the main entry, since it is the default and always present. The wider spaces are behind their own entry point, so an app that never imports them never ships the matrices:

import { SRGB, inGamut, clampToGamut } from "@oklch-picker/core";
import { P3, REC2020 } from "@oklch-picker/core/gamuts";

inGamut({ l: 0.7, c: 0.25, h: 145 }, SRGB);   // false
inGamut({ l: 0.7, c: 0.25, h: 145 }, P3);     // true

// Clamping targets a space too: chroma is reduced until it fits,
// keeping lightness and hue.
clampToGamut({ l: 0.7, c: 0.35, h: 145 }, P3);

Which space each format writes

Only oklch() can express a colour wider than sRGB. The other formats are sRGB notations, so each takes a colour and no gamut: there is nothing a space argument could change, since a browser reads their numbers as sRGB either way.

WritesSpaceA P3 or Rec. 2020 colour
formatOklchanykept exactly
formatRgbsRGBclamped to the nearest sRGB colour
formatHslsRGBclamped to the nearest sRGB colour
formatHwbsRGBclamped to the nearest sRGB colour
oklchToHexsRGBclamped to the nearest sRGB colour
oklchToRgb255the gamut you passthat space's own channels, for boundary maths rather than CSS

Reading is symmetric: toOklch accepts all five, so a value can arrive in any of them. What a clamped format cannot do is give the wider colour back, which is why value stores oklch(). See Wider gamuts.

oklchToRgb255 is the one exception, and it is not a CSS format. It returns a space's own 0..255 channels, which is what the chart boundaries need. Those numbers are not valid in an rgb()string unless the space is sRGB.

The headless model

pickerModel() returns everything a picker needs for one render: axis ranges, track gradients, chart geometry, and the draft/emit resolution. It is what every adapter is built on, and it is exported for anyone building a sixth.

Behaviour lives in the model rather than in any adapter, so a change there reaches them all at once. If you are wiring up your own UI, that is the layer to build against.