@colordx/core

Try it on colordx.dev
A modern color manipulation library built for the CSS Color 4 era, with first-class support for OKLCH and OKLab. 7.6 KB gzipped. 0 Dependencies.
Performance
Benchmarks run on Apple M4, Node.js 22, using mitata. Operations per second — higher is better.
| HEX → toHsl | 24M | — | 10M | 5.5M | 3.5M | 2.8M | 2.5M |
| HEX → lighten → toHex | 12M | — | 5.8M | 4.8M | 1.3M | 1.0M | 1.0M |
| Mix two colors | 6.7M | 5.2M | 1.2M | 1.0M | 1.1M | 550K | 1.1M |
| HEX → toOklch | 5.5M | 4.5M | — | 3.3M | 1.0M | 2.0M | — |
| inGamutP3 | 4.6M | 3.0M | — | 1.0M | — | — | — |
| inGamutRec2020 | 4.5M | 3.2M | — | 1.1M | — | — | — |
Install
npm install @colordx/core
Quick start
import { colordx } from '@colordx/core';
colordx('#ff0000').toRgbString();
colordx('#ff0000').toHex();
colordx('#ff0000').toOklch();
colordx('#ff0000').toOklchString();
colordx('oklch(0.5 0.2 240)').toHex();
colordx({ r: 255, g: 0, b: 0 }).toHslString();
colordx('#ff0000').lighten(0.1).saturate(0.2).toHex();
colordx('#3d7a9f').rotate(30).darken(0.1).toRgbString();
The colordx() factory is all you need for day-to-day work. For out-of-gamut oklch() / oklab() inputs, .toHex() / .toRgbString() clip in linear sRGB — the same strategy browsers use when rendering background: oklch(...) — so your output matches what users see on screen. If you need stricter hue/lightness preservation for authoring workflows, see Gamut.
API
All methods are immutable — they return a new Colordx instance.
Parsing
Accepts any CSS color string or color object:
colordx('#ff0000');
colordx('#f00');
colordx('rgb(255 0 0)');
colordx('rgba(255, 0, 0, 0.5)');
colordx('hsl(0 100% 50%)');
colordx('oklab(0.6279 0.2249 0.1257)');
colordx('oklch(0.6279 0.2577 29.23)');
colordx({ r: 255, g: 0, b: 0 });
colordx({ r: 255, g: 0, b: 0, alpha: 0.5 });
colordx({ h: 0, s: 100, l: 50 });
colordx({ l: 0.6279, a: 0.2249, b: 0.1257 });
colordx({ l: 0.6279, c: 0.2577, h: 29.23 });
colordx('color(display-p3 0.9176 0.2003 0.1386)');
colordx('color(rec2020 0.7919 0.2307 0.0739)');
colordx('color(a98-rgb 0.8586 0 0)');
colordx('color(prophoto-rgb 0.7022 0.2757 0.1035)');
colordx('hwb(0 0% 0%)');
colordx({ h: 0, w: 0, b: 0 });
colordx({ h: 0, s: 100, v: 100 });
TypeScript: input color objects use *ColorInput types (alpha optional, defaults to 1).
Output methods like .toRgb() / .toOklch() return *Color types (alpha always present).
import type { RgbColor, RgbColorInput } from '@colordx/core';
const input: RgbColorInput = { r: 255, g: 0, b: 0 };
const output: RgbColor = colordx(input).toRgb();
Conversion
.toRgb()
.toRgbString()
.toRgbString({ legacy: true })
colordx({ r: 255, g: 0, b: 0, alpha: 0.5 }).toRgbString({ legacy: true });
.toHex()
.toNumber()
.toHsl()
.toHslString()
colordx('#3d7a9f').toHsl()
colordx('#3d7a9f').toHsl(4)
colordx('#3d7a9f').toHsl(0)
colordx('#3d7a9f').toHslString()
colordx('#3d7a9f').toHslString(4)
.toHwb()
.toHwbString()
.toOklab()
.toOklabString()
.toOklch()
.toOklchString()
.toP3()
.toP3String()
Manipulation
.lighten(0.1)
.lighten(0.1, { relative: true })
.darken(0.1)
.darken(0.1, { relative: true })
.saturate(0.1)
.saturate(0.1, { relative: true })
.desaturate(0.1)
.desaturate(0.1, { relative: true })
.grayscale()
.invert()
.rotate(30)
.alpha(0.5)
.hue(120)
.lightness(0.5)
.chroma(0.1)
Getters
.isValid()
.alpha()
.hue()
.lightness()
.chroma()
.brightness()
.isDark()
.isLight()
.isEqual('#f00')
.luminance()
.contrast('#fff')
.mix('#0000ff', 0.5)
.mixOklab('#0000ff', 0.5)
Utilities
import { getFormat, nearest, oklchToLinear, oklchToRgbChannels, random } from '@colordx/core';
getFormat('#ff0000');
getFormat('rgb(255 0 0)');
getFormat('hsl(0 100% 50%)');
getFormat('oklch(0.5 0.2 240)');
getFormat('oklab(0.6279 0.2249 0.1257)');
getFormat({ r: 255, g: 0, b: 0 });
getFormat({ h: 0, s: 100, l: 50 });
getFormat('notacolor');
nearest('#800', ['#f00', '#ff0', '#00f']);
nearest('#ffe', ['#f00', '#ff0', '#00f']);
random();
oklchToRgbChannels(0.5, 0.2, 240);
const linear = oklchToLinear(0.5, 0.2, 240);
import {
labToLinearAndSrgb,
labToLinearSrgb,
labToRgbChannels,
lchToLinearAndSrgb,
lchToLinearSrgb,
lchToRgbChannels,
rgbToLinear,
} from '@colordx/core';
rgbToLinear(1, 0, 0);
labToLinearSrgb(54.29, 80.8, 69.89);
lchToLinearSrgb(54.29, 106.84, 40.86);
labToRgbChannels(54.29, 80.8, 69.89);
lchToRgbChannels(54.29, 106.84, 40.86);
const [lin, srgb] = labToLinearAndSrgb(54.29, 80.8, 69.89);
const { r, g, b } = colordx('#ff0000').toRgb();
rgbToLinear(r / 255, g / 255, b / 255);
import { labToP3Channels, lchToP3Channels, linearToP3Channels, oklchToP3Channels } from '@colordx/core/plugins/p3';
import {
labToRec2020Channels,
lchToRec2020Channels,
linearToRec2020Channels,
oklchToRec2020Channels,
} from '@colordx/core/plugins/rec2020';
oklchToP3Channels(0.5, 0.2, 240);
oklchToRec2020Channels(0.5, 0.2, 240);
labToP3Channels(54.29, 80.8, 69.89);
lchToP3Channels(54.29, 106.84, 40.86);
labToRec2020Channels(54.29, 80.8, 69.89);
lchToRec2020Channels(54.29, 106.84, 40.86);
linearToP3Channels(...linear);
linearToRec2020Channels(...linear);
Zero-allocation tight-loop variants (*Into). Every channel function has an *Into sibling that writes into a caller-provided Float64Array | number[] instead of allocating a new tuple. For per-pixel work (canvas renderers, gradient grids, wide-gamut data viz), this eliminates ~10× the GC pressure and makes interactive redraws smoother. Output is bit-for-bit identical to the allocating version.
import {
oklchToLinearInto,
oklchToRgbChannelsInto,
oklchToLinearAndSrgbInto,
} from '@colordx/core';
import { linearToP3ChannelsInto, oklchToP3ChannelsInto } from '@colordx/core/plugins/p3';
import { linearToRec2020ChannelsInto, oklchToRec2020ChannelsInto } from '@colordx/core/plugins/rec2020';
const buf = new Float64Array(3);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const [l, c, h] = getOklch(x, y);
oklchToP3ChannelsInto(buf, l, c, h);
imageData[i++] = Math.floor(buf[0] * 255);
imageData[i++] = Math.floor(buf[1] * 255);
imageData[i++] = Math.floor(buf[2] * 255);
imageData[i++] = 255;
}
}
Full *Into surface (all tree-shakable — unused ones have zero bundle cost):
oklchToLinearInto(out, l, c, h);
oklchToRgbChannelsInto(out, l, c, h);
oklchToLinearAndSrgbInto(linOut, srgbOut, l, c, h);
rgbToLinearInto(out, r, g, b);
labToLinearSrgbInto(out, l, a, b);
labToRgbChannelsInto(out, l, a, b);
labToLinearAndSrgbInto(linOut, srgbOut, l, a, b);
lchToLinearSrgbInto(out, l, c, h);
lchToRgbChannelsInto(out, l, c, h);
lchToLinearAndSrgbInto(linOut, srgbOut, l, c, h);
linearToP3ChannelsInto(out, lr, lg, lb);
oklchToP3ChannelsInto(out, l, c, h);
labToP3ChannelsInto(out, l, a, b);
lchToP3ChannelsInto(out, l, c, h);
linearToRec2020ChannelsInto(out, lr, lg, lb);
oklchToRec2020ChannelsInto(out, l, c, h);
labToRec2020ChannelsInto(out, l, a, b);
lchToRec2020ChannelsInto(out, l, c, h);
Guidance:
- Use
Float64Array(3) for the buffer when you can — it's the convention and keeps the V8 call site monomorphic. number[] also works.
- One buffer per loop is plenty; don't allocate per iteration.
linOut and srgbOut in oklchToLinearAndSrgbInto must be distinct buffers (the function writes to both).
- If you're outside a hot loop, the regular allocating versions are more ergonomic — reach for
*Into only when you've profiled and GC is the bottleneck.
Gamut
oklch() and oklab() can describe colors outside the sRGB gamut. For everyday conversion, .toRgbString() / .toHex() already do the right thing — they naive-clip in linear sRGB to match browser rendering, so your output matches what background: oklch(...) displays on screen. You only need the methods below when that default isn't what you want.
Internally, out-of-gamut oklch() / oklab() inputs are stored unclamped, so the authored color is preserved losslessly. That means .toOklchString() round-trips the original, and you can choose when (and how) to fold the color into sRGB:
const input = 'oklch(0.5 0.4 180)';
colordx(input).toOklchString();
colordx(input).toRgbString();
colordx(input).mapSrgb().toOklchString();
colordx(input).mapSrgb().toRgbString();
colordx(input).clampSrgb().toOklchString();
colordx(input).clampSrgb().toRgbString();
.mapSrgb() — CSS Color 4 chroma-reduction binary search. Preserves lightness and hue; sacrifices chroma. Use when hue stability matters — design tokens, palettes, programmatic harmonies, OKLCH pickers.
.clampSrgb() — naive clip in linear sRGB. Hue and lightness may drift. Use when you want a Colordx whose .toOklchString() describes what browsers actually render.
A static form is also available for one-shot conversion without wrapping first — Colordx.toGamutSrgb(input) is equivalent to colordx(input).mapSrgb().
colordx also includes standalone utilities for checking and mapping into wider gamuts (Display-P3 / Rec.2020, via plugins):
import { Colordx, inGamutSrgb } from '@colordx/core';
import { inGamutP3 } from '@colordx/core/plugins/p3';
import { inGamutRec2020 } from '@colordx/core/plugins/rec2020';
import p3 from '@colordx/core/plugins/p3';
import rec2020 from '@colordx/core/plugins/rec2020';
extend([p3, rec2020]);
inGamutSrgb('#ff0000');
inGamutSrgb('oklch(0.5 0.1 30)');
inGamutSrgb('oklch(0.5 0.4 180)');
Colordx.toGamutSrgb('oklch(0.5 0.4 180)');
Colordx.toGamutSrgb('#ff0000');
inGamutP3('oklch(0.64 0.27 29)');
inGamutP3('oklch(0.5 0.4 180)');
Colordx.toGamutP3('oklch(0.5 0.4 180)');
inGamutRec2020('oklch(0.5 0.4 180)');
Colordx.toGamutRec2020('oklch(0.5 0.4 180)');
Gamut containment is largely hierarchical: sRGB ⊂ Display-P3 ⊂ Rec.2020 ⊂ ProPhoto. A98 (Adobe RGB 1998) sits between sRGB and Rec.2020 — wider than sRGB, mostly in the greens — but is not a strict superset of Display-P3. All inGamut* functions always return true for sRGB-bounded inputs (hex, rgb, hsl, hsv, hwb). The toGamut* functions use a binary chroma-reduction search following the CSS Color 4 gamut mapping algorithm.
Gamut checks and mapping accept wide-gamut inputs in every supported form — oklab() / oklch(), CIE lab() / lch() strings, and the corresponding object shapes (including branded { colorSpace: 'lab' | 'lch' } objects):
const lch = { l: 50, c: 100, h: 180, alpha: 1, colorSpace: 'lch' as const };
inGamutSrgb(lch);
inGamutP3(lch);
colordx(lch).toHex();
colordx(lch).mapSrgb().toHex();
inGamutSrgb('lab(50 100 0)');
Colordx.toGamutSrgb('lab(50 100 0)');
Plugins
Opt-in plugins for less common color spaces and utilities:
import { extend } from '@colordx/core';
import a11y from '@colordx/core/plugins/a11y';
import cmyk from '@colordx/core/plugins/cmyk';
import harmonies from '@colordx/core/plugins/harmonies';
import hwb from '@colordx/core/plugins/hwb';
import hsv from '@colordx/core/plugins/hsv';
import lab from '@colordx/core/plugins/lab';
import lch from '@colordx/core/plugins/lch';
import minify from '@colordx/core/plugins/minify';
import mix from '@colordx/core/plugins/mix';
import names from '@colordx/core/plugins/names';
import p3 from '@colordx/core/plugins/p3';
import rec2020 from '@colordx/core/plugins/rec2020';
import a98rgb from '@colordx/core/plugins/a98rgb';
import prophoto from '@colordx/core/plugins/prophoto';
extend([lab, lch, cmyk, names, a11y, harmonies, hwb, hsv, mix, minify, p3, rec2020, a98rgb, prophoto]);
lab plugin
CIE Lab (D50), CIE XYZ (D50), and CIE XYZ (D65) color models. Lab and XYZ objects are also accepted as color input (Lab requires a colorSpace: 'lab' discriminant; XYZ D65 requires colorSpace: 'xyz-d65'; plain { x, y, z } parses as D50). Also adds .mixLab() for perceptual mixing in CIE Lab, .delta() for CIEDE2000 color difference, and string conversion methods.
import lab from '@colordx/core/plugins/lab';
extend([lab]);
colordx('#ff0000').toLab();
colordx('#ff0000').toLabString();
colordx('lab(54.29 80.8 69.89)').toHex();
colordx('#ff0000').toXyz();
colordx('#ff0000').toXyzString();
colordx('#ff0000').toXyzD65();
colordx('#ff0000').toXyzD65String();
colordx({ l: 54.29, a: 80.8, b: 69.89, colorSpace: 'lab' as const }).toHex();
colordx({ x: 43.61, y: 22.25, z: 1.39 }).toHex();
colordx({ x: 41.24, y: 21.26, z: 1.93, colorSpace: 'xyz-d65' as const }).toHex();
colordx('color(xyz-d50 43.61 22.25 1.39)').toHex();
colordx('color(xyz-d65 41.24 21.26 1.93)').toHex();
colordx('#000000').mixLab('#ffffff').toHex();
colordx('#ff0000').delta('#ff0000');
colordx('#000000').delta('#ffffff');
colordx('#ff0000').delta();
lch plugin
CIE LCH (D50) — the polar form of CIE Lab. Parses lch() CSS strings and LCH objects.
import lch from '@colordx/core/plugins/lch';
extend([lch]);
colordx('#ff0000').toLch();
colordx('#ff0000').toLchString();
colordx('lch(54.29 106.84 40.86)').toHex();
colordx({ l: 50, c: 50, h: 180, colorSpace: 'lch' as const }).toHex();
cmyk plugin
CMYK color model. Parses device-cmyk() CSS strings and CMYK objects.
import cmyk from '@colordx/core/plugins/cmyk';
extend([cmyk]);
colordx('#ff0000').toCmyk();
colordx('#ff0000').toCmykString();
colordx('device-cmyk(0% 100% 100% 0%)').toHex();
colordx({ c: 0, m: 100, y: 100, k: 0 }).toHex();
names plugin
CSS named color support (140 names from the CSS spec). toName() returns undefined for colors with no CSS name.
import names from '@colordx/core/plugins/names';
extend([names]);
colordx('red').toHex();
colordx('rebeccapurple').toHex();
colordx('#ff0000').toName();
colordx('#c06060').toName();
colordx('#c06060').toName({ closest: true });
hsv plugin
HSV/HSVa color model. Parses hsv() / hsva() strings and HSV objects.
import hsv from '@colordx/core/plugins/hsv';
extend([hsv]);
colordx('#ff0000').toHsv();
colordx('#ff0000').toHsvString();
colordx('hsv(0 100% 100%)').toHex();
colordx({ h: 0, s: 100, v: 100, alpha: 1 }).toHex();
harmonies plugin
Color harmony generation using hue rotation.
import harmonies from '@colordx/core/plugins/harmonies';
extend([harmonies]);
colordx('#ff0000').harmonies();
colordx('#ff0000').harmonies('complementary');
colordx('#ff0000').harmonies('analogous');
colordx('#ff0000').harmonies('split-complementary');
colordx('#ff0000').harmonies('triadic');
colordx('#ff0000').harmonies('tetradic');
colordx('#ff0000').harmonies('rectangle');
colordx('#ff0000').harmonies('double-split-complementary');
hwb plugin
CSS Color Level 4 HWB (Hue, Whiteness, Blackness) color model.
import hwb from '@colordx/core/plugins/hwb';
extend([hwb]);
colordx('#ff0000').toHwb();
colordx('#ff0000').toHwbString();
colordx('hwb(0 0% 0%)').toHex();
colordx({ h: 0, w: 0, b: 0, alpha: 1 }).toHex();
colordx('#3d7a9f').toHwb();
colordx('#3d7a9f').toHwb(2);
colordx('#3d7a9f').toHwbString();
colordx('#3d7a9f').toHwbString(2);
mix plugin
Color mixing helpers built on top of .mix().
import mix from '@colordx/core/plugins/mix';
extend([mix]);
colordx('#ff0000').tints(5);
colordx('#ff0000').shades(3);
colordx('#ff0000').tones(3);
colordx('#ff0000').palette(3, '#0000ff');
minify plugin
Returns the shortest valid CSS representation of a color. By default tries hex, RGB, and HSL and picks the shortest.
import minify from '@colordx/core/plugins/minify';
extend([minify]);
colordx('#ff0000').minify();
colordx('#ffffff').minify();
colordx('#ff0000').minify({ name: true });
colordx({ r: 0, g: 0, b: 0, a: 0 }).minify({ transparent: true });
colordx({ r: 255, g: 0, b: 0, a: 0.5 }).minify({ alphaHex: true });
colordx('#ff0000').minify({ hsl: false });
a11y plugin
WCAG 2.x contrast:
colordx('#000').isReadable('#fff');
colordx('#000').isReadable('#fff', { level: 'AAA' });
colordx('#000').isReadable('#fff', { size: 'large' });
colordx('#000').readableScore('#fff');
colordx('#e60000').readableScore('#ffff47');
colordx('#949494').readableScore('#fff');
colordx('#aaa').readableScore('#fff');
colordx('#777').minReadable('#fff');
APCA (Accessible Perceptual Contrast Algorithm) — the projected replacement for WCAG 2.x in WCAG 3.0:
colordx('#000').apcaContrast('#fff');
colordx('#fff').apcaContrast('#000');
colordx('#202122').apcaContrast('#cf674a');
colordx('#ffffff').apcaContrast('#cf674a');
colordx('#000').isReadableApca('#fff');
colordx('#777').isReadableApca('#fff');
colordx('#777').isReadableApca('#fff', { size: 'large' });
APCA is better suited than WCAG 2.x for dark color pairs and more accurately reflects human perception. See Introduction to APCA for background.
p3 plugin
Adds Display-P3 color space support. P3 has a wider gamut than sRGB and is natively supported by all modern browsers and most Mac/iOS displays.
import p3 from '@colordx/core/plugins/p3';
extend([p3]);
colordx('#ff0000').toP3();
colordx('#ff0000').toP3String();
colordx('color(display-p3 0.9175 0.2003 0.1386)').toHex();
colordx('color(display-p3 0.9175 0.2003 0.1386 / 0.5)').toHex();
The plugin also exports standalone gamut utilities and low-level channel functions. inGamutP3 and the channel helpers need no extend(). Gamut mapping is available as Colordx.toGamutP3 after extend([p3]):
import { Colordx, extend } from '@colordx/core';
import p3, { inGamutP3, linearToP3Channels, oklchToP3Channels } from '@colordx/core/plugins/p3';
extend([p3]);
inGamutP3('oklch(0.64 0.27 29)');
Colordx.toGamutP3('oklch(0.5 0.4 180)');
oklchToP3Channels(0.5, 0.2, 240);
Object parsing is also supported using the colorSpace discriminant:
colordx({ r: 0.9505, g: 0.2856, b: 0.0459, alpha: 1, colorSpace: 'display-p3' }).toHex();
rec2020 plugin
Adds Rec.2020 (BT.2020) color space support. Rec.2020 has the widest gamut of the three — it covers most of the visible spectrum.
import rec2020 from '@colordx/core/plugins/rec2020';
extend([rec2020]);
colordx('#ff0000').toRec2020();
colordx('#ff0000').toRec2020String();
colordx('color(rec2020 0.792 0.231 0.0738)').toHex();
colordx('color(rec2020 0.792 0.231 0.0738 / 0.5)').toHex();
The plugin also exports standalone gamut utilities and low-level channel functions. inGamutRec2020 and the channel helpers need no extend(). Gamut mapping is available as Colordx.toGamutRec2020 after extend([rec2020]):
import { Colordx, extend } from '@colordx/core';
import rec2020, { inGamutRec2020, linearToRec2020Channels, oklchToRec2020Channels } from '@colordx/core/plugins/rec2020';
extend([rec2020]);
inGamutRec2020('oklch(0.5 0.4 180)');
Colordx.toGamutRec2020('oklch(0.5 0.4 180)');
oklchToRec2020Channels(0.5, 0.2, 240);
Object parsing is also supported using the colorSpace discriminant:
colordx({ r: 0.7919, g: 0.2307, b: 0.0739, alpha: 1, colorSpace: 'rec2020' }).toHex();
a98rgb plugin
Adds A98 (Adobe RGB 1998) color space support. A98 shares sRGB's red and blue primaries but a wider green primary, so it extends mainly into the greens/cyans. It is a CSS Color 4 predefined space and a long-standing photography/print working space.
import a98rgb from '@colordx/core/plugins/a98rgb';
extend([a98rgb]);
colordx('#ff0000').toA98();
colordx('#ff0000').toA98String();
colordx('color(a98-rgb 0.8586 0 0)').toHex();
colordx('color(a98-rgb 0.8586 0 0 / 0.5)').toHex();
The plugin also exports standalone gamut utilities and low-level channel functions. inGamutA98 and the channel helpers need no extend(). Gamut mapping is available as Colordx.toGamutA98 after extend([a98rgb]):
import { Colordx, extend } from '@colordx/core';
import a98rgb, { inGamutA98, linearToA98Channels, oklchToA98Channels } from '@colordx/core/plugins/a98rgb';
extend([a98rgb]);
inGamutA98('oklch(0.7 0.25 150)');
Colordx.toGamutA98('oklch(0.5 0.4 180)');
oklchToA98Channels(0.5, 0.2, 240);
Object parsing is also supported using the colorSpace discriminant:
colordx({ r: 0.8586, g: 0, b: 0, alpha: 1, colorSpace: 'a98-rgb' }).toHex();
prophoto plugin
Adds ProPhoto (ROMM RGB) color space support. ProPhoto has an extremely wide gamut — wider than Rec.2020, covering most of the visible spectrum — and uses a D50 white point with a gamma-1.8 transfer curve. It is the standard working space for high-end RAW photo and print pipelines.
import prophoto from '@colordx/core/plugins/prophoto';
extend([prophoto]);
colordx('#ff0000').toProphoto();
colordx('#ff0000').toProphotoString();
colordx('color(prophoto-rgb 0.7022 0.2757 0.1035)').toHex();
colordx('color(prophoto-rgb 0.7022 0.2757 0.1035 / 0.5)').toHex();
The plugin also exports standalone gamut utilities and low-level channel functions. inGamutProphoto and the channel helpers need no extend(). Gamut mapping is available as Colordx.toGamutProphoto after extend([prophoto]):
import { Colordx, extend } from '@colordx/core';
import prophoto, { inGamutProphoto, linearToProphotoChannels, oklchToProphotoChannels } from '@colordx/core/plugins/prophoto';
extend([prophoto]);
inGamutProphoto('oklch(0.5 0.4 180)');
Colordx.toGamutProphoto('oklch(0.5 0.4 180)');
oklchToProphotoChannels(0.5, 0.2, 240);
Object parsing is also supported using the colorSpace discriminant:
colordx({ r: 0.7022, g: 0.2757, b: 0.1035, alpha: 1, colorSpace: 'prophoto-rgb' }).toHex();
Notes
mix() uses sRGB; use mixLab() or mixOklab() for perceptual blending
mix() interpolates in sRGB, matching CSS color-mix(in srgb, ...) and how browsers composite layers. Use mixOklab() for perceptually uniform blending, or mixLab() (lab plugin) for CIE Lab.
colordx('#000000').mix('#ffffff').toHex();
colordx('#000000').mixOklab('#ffffff').toHex();
import lab from '@colordx/core/plugins/lab';
extend([lab]);
colordx('#000000').mixLab('#ffffff').toHex();
The same applies to tints(), shades(), and tones() from the mix plugin, which all call .mix() internally.
Precision
Every toX() / toXString() method accepts an optional precision (decimal places), applied uniformly to every channel of that format. Alpha is fixed at 3 dp globally. Format-specific defaults (scale-appropriate):
toHsl, toHsv, toCmyk, toLab, toLch, toXyz, toXyzD65 | 2 |
toHwb | 0 |
toOklab, toOklch, toP3, toRec2020 | 4 |
colordx('#3d7a9f').toHsl();
colordx('#3d7a9f').toHsl(4);
colordx('#3d7a9f').toHsl(0);
colordx('#ff0000').toOklchString();
colordx('#ff0000').toOklchString(2);
The minify() plugin preserves full HSL precision when building candidates, so minification is lossless — it only picks HSL when the string is genuinely shorter than hex/rgb.
Relative lighten/darken
By default, .lighten(0.1) shifts lightness by an absolute 10 percentage points. Pass { relative: true } to shift by a fraction of the current value instead — useful when migrating from Qix's color library or when you want proportional adjustments:
colordx('#1a0000').lighten(0.1);
colordx('#1a0000').lighten(0.1, { relative: true });
colordx('#a35050').saturate(0.1);
colordx('#a35050').saturate(0.1, { relative: true });
The same flag works on .darken() and .desaturate().
Roadmap
CSS Color 4/5 completeness
color-mix() — parse and evaluate color-mix(in oklch, red 30%, blue) strings, with support for all interpolation spaces and polar hue methods (shorter, longer, increasing, decreasing)
color() for remaining spaces — color(srgb ...), color(srgb-linear ...), color(a98-rgb ...), color(prophoto-rgb ...) string parsing (display-p3, rec2020, xyz-d50, and xyz-d65 already supported)
- Relative color syntax —
oklch(from red l c h) and channel arithmetic like oklch(from red l calc(c + 0.1) h)
Internals
- Deduplicate the sRGB→XYZ D65 matrix shared between
xyz.ts and lab.ts
Ecosystem
- @colordx/gpu — colordx's color math on the GPU: conversions and gamut tests as generated GLSL, parity-tested against this library. For per-pixel workloads — picker charts, gamut visualizations, gradients — where you convert millions of colors per frame instead of one at a time.
Supported by Ruslo.app.
License
MIT