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 fitsWhat is exported
| Function | Does |
|---|---|
toOklch, parseOklch, formatOklch | Parse and format |
hexToOklch, oklchToHex | Convert, exact round-trip within sRGB |
parseRgb, formatRgb, oklchToRgb255 | The same for rgb() and rgba() |
parseHsl, formatHsl | The same for hsl() and hsla() |
parseHwb, formatHwb | The same for hwb() |
inGamut, clampToGamut, maxChroma | Gamut queries |
gamutCurve | Cross-section data behind the charts |
colourName | "Dark pink", "Muted teal", "Light grey" |
isLight | WCAG luminance, for readable text over a swatch |
addRecent | The 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.
| Writes | Space | A P3 or Rec. 2020 colour |
|---|---|---|
formatOklch | any | kept exactly |
formatRgb | sRGB | clamped to the nearest sRGB colour |
formatHsl | sRGB | clamped to the nearest sRGB colour |
formatHwb | sRGB | clamped to the nearest sRGB colour |
oklchToHex | sRGB | clamped to the nearest sRGB colour |
oklchToRgb255 | the gamut you pass | that 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.