A modern color manipulation library built for the CSS Color 4 era. The successor to colord with first-class support for OKLCH and OKLab. 3 KB gzipped. More than 2× faster than colord.
colord is a great library, but it was designed around CSS Color 3. Modern CSS uses oklch() and oklab() — color spaces that produce better gradients, more accurate lightness adjustments, and consistent hue shifts. colord has no support for them, not even via a plugin. With colordx, they're built in.
Benchmarks run on Apple M4, Node.js 22, using mitata. Operations per second — higher is better.
| Benchmark | colordx | colord | culori | chroma-js | color | tinycolor2 |
|---|---|---|---|---|---|---|
| HEX → toHsl | 25M | 11M | 6.6M | 3.4M | 2.8M | 2.4M |
| HEX → lighten → toHex | 15M | 5.9M | 4.9M | 1.3M | 1.0M | 1.0M |
| Mix two colors | 7.7M | 1.3M | 1.1M | 1.2M | 546K | 1.1M |
| HEX → toOklch | 5.4M | — | 3.4M | 1.0M | 2.0M | — |
| inGamutP3 | 3.7M | — | 1.1M | — | — | — |
| inGamutRec2020 | 4.6M | — | 1.1M | — | — | — |
npm install @colordx/coreimport { colordx } from '@colordx/core';
colordx('#ff0000').toOklch(); // { l: 0.628, c: 0.2577, h: 29.23, alpha: 1 }
colordx('#ff0000').toOklchString(); // 'oklch(0.628 0.2577 29.23)'
colordx('#ff0000').lighten(0.1).toHex(); // '#ff3333'
colordx('oklch(0.5 0.2 240)').toHex(); // '#0069c7'All methods are immutable — they return a new Colordx instance.
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, alpha: 1 });
colordx({ h: 0, s: 100, l: 50, alpha: 1 });
colordx({ l: 0.6279, a: 0.2249, b: 0.1257, alpha: 1 }); // OKLab
colordx({ l: 0.6279, c: 0.2577, h: 29.23, alpha: 1 }); // OKLch
// With p3 plugin loaded:
colordx('color(display-p3 0.9176 0.2003 0.1386)'); // Display-P3 string
// With rec2020 plugin loaded:
colordx('color(rec2020 0.7919 0.2307 0.0739)'); // Rec.2020 string
// With hwb plugin loaded:
colordx('hwb(0 0% 0%)');
colordx({ h: 0, w: 0, b: 0, alpha: 1 });
// With hsv plugin loaded:
colordx({ h: 0, s: 100, v: 100, alpha: 1 }); // HSV.toRgb() // { r: 255, g: 0, b: 0, alpha: 1 }
.toRgbString() // 'rgb(255, 0, 0)'
.toHex() // '#ff0000'
.toNumber() // 16711680 (0xff0000 — PixiJS / Discord integer format)
.toHsl() // { h: 0, s: 100, l: 50, alpha: 1 }
.toHslString() // 'hsl(0, 100%, 50%)'
// toHsl accepts an optional precision argument (decimal places):
colordx('#3d7a9f').toHsl() // { h: 202.65, s: 44.55, l: 43.14, alpha: 1 } — default (2)
colordx('#3d7a9f').toHsl(4) // { h: 202.6531, s: 44.5455, l: 43.1373, alpha: 1 }
colordx('#3d7a9f').toHsl(0) // { h: 203, s: 45, l: 43, alpha: 1 } — integers
colordx('#3d7a9f').toHslString() // 'hsl(202.65, 44.55%, 43.14%)'
colordx('#3d7a9f').toHslString(4) // 'hsl(202.6531, 44.5455%, 43.1373%)'
// With hwb plugin loaded:
.toHwb() // { h: 0, w: 0, b: 0, alpha: 1 }
.toHwbString() // 'hwb(0 0% 0%)'
.toOklab() // { l: 0.628, a: 0.2249, b: 0.1258, alpha: 1 }
.toOklabString() // 'oklab(0.628 0.2249 0.1258)'
.toOklch() // { l: 0.628, c: 0.2577, h: 29.23, alpha: 1 }
.toOklchString() // 'oklch(0.628 0.2577 29.23)'
// With p3 plugin loaded:
.toP3() // { r: 0.9175, g: 0.2003, b: 0.1386, alpha: 1, colorSpace: 'display-p3' }
.toP3String() // 'color(display-p3 0.9175 0.2003 0.1386)'.lighten(0.1) // increase lightness by 10 percentage points
.lighten(0.1, { relative: true }) // increase lightness by 10% of current value
.darken(0.1) // decrease lightness by 10 percentage points
.darken(0.1, { relative: true }) // decrease lightness by 10% of current value
.saturate(0.1) // increase saturation by 10 percentage points
.saturate(0.1, { relative: true }) // increase saturation by 10% of current value
.desaturate(0.1) // decrease saturation by 10 percentage points
.desaturate(0.1, { relative: true }) // decrease saturation by 10% of current value
.grayscale() // fully desaturate
.invert() // invert RGB channels
.rotate(30) // rotate hue by 30°
.alpha(0.5) // set alpha
.hue(120) // set hue (HSL)
.lightness(0.5) // set lightness (OKLCH, 0–1)
.chroma(0.1) // set chroma (OKLCH, 0–0.4).isValid() // true if input was parseable
.alpha() // get alpha (0–1)
.hue() // get hue (0–360)
.lightness() // get OKLCH lightness (0–1)
.chroma() // get OKLCH chroma (0–0.4)
.brightness() // perceived brightness (0–1)
.isDark() // brightness < 0.5
.isLight() // brightness >= 0.5
.isEqual('#f00') // exact RGB equality
// With a11y plugin loaded:
.luminance() // relative luminance (0–1, WCAG)
.contrast('#fff') // WCAG 2.x contrast ratio (1–21)
// With mix plugin loaded:
.mix('#0000ff', 0.5) // mix in sRGB space (CSS spec)
.mixOklab('#0000ff', 0.5) // mix in Oklab space (perceptually uniform)import { getFormat, nearest, oklchToLinear, oklchToRgbChannels, random } from '@colordx/core';
getFormat('#ff0000'); // 'hex'
getFormat('rgb(255, 0, 0)'); // 'rgb'
getFormat('hsl(0, 100%, 50%)'); // 'hsl'
getFormat('oklch(0.5 0.2 240)'); // 'oklch'
getFormat('oklab(0.6279 0.2249 0.1257)'); // 'oklab'
getFormat({ r: 255, g: 0, b: 0, alpha: 1 }); // 'rgb'
getFormat({ h: 0, s: 100, l: 50, alpha: 1 }); // 'hsl'
getFormat('notacolor'); // undefined
// Plugin-added parsers register their own format:
// p3 → 'p3', hsv → 'hsv', cmyk → 'cmyk', lch → 'lch', lab → 'lab', xyz → 'xyz', names → 'name', rec2020 → 'rec2020'
nearest('#800', ['#f00', '#ff0', '#00f']); // '#f00' — perceptual distance via OKLab
nearest('#ffe', ['#f00', '#ff0', '#00f']); // '#ff0'
random(); // random Colordx instance
// Low-level functional converters — no object allocation, for hot paths (canvas gradients, etc.)
oklchToRgbChannels(0.5, 0.2, 240); // [r, g, b] gamma-encoded sRGB in [0, 1]
// Out-of-gamut channels may exceed [0, 1] — callers clamp before byte encoding
const linear = oklchToLinear(0.5, 0.2, 240); // unclamped linear sRGB — also a free sRGB gamut check
// P3/Rec.2020 channel functions live in their plugins:
import { linearToP3Channels, oklchToP3Channels } from '@colordx/core/plugins/p3';
import { linearToRec2020Channels, oklchToRec2020Channels } from '@colordx/core/plugins/rec2020';
oklchToP3Channels(0.5, 0.2, 240); // [r, g, b] gamma-encoded Display-P3 in [0, 1]
oklchToRec2020Channels(0.5, 0.2, 240); // [r, g, b] gamma-encoded Rec.2020 in [0, 1] (BT.2020 gamma)
// Split-step API: compute the shared expensive OKLCH→linear sRGB step once,
// then apply cheap per-space steps to avoid repeating 3× Math.cbrt + OKLab matrix.
linearToP3Channels(...linear); // linear sRGB → gamma-encoded P3
linearToRec2020Channels(...linear); // linear sRGB → gamma-encoded Rec.2020 (BT.2020 gamma)OKLCH and OKLab can describe colors outside the sRGB gamut. colordx includes standalone utilities for checking and mapping colors into sRGB (core) and Display-P3 / Rec.2020 (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]);
// Check: is this color displayable in sRGB?
inGamutSrgb('#ff0000'); // true — hex is always sRGB
inGamutSrgb('oklch(0.5 0.1 30)'); // true — clearly in sRGB
inGamutSrgb('oklch(0.5 0.4 180)'); // false — too much cyan chroma
// Map: reduce chroma until in-gamut (preserves lightness and hue)
Colordx.toGamutSrgb('oklch(0.5 0.4 180)'); // → Colordx at the sRGB boundary
Colordx.toGamutSrgb('#ff0000'); // → unchanged, already in sRGB
// Display-P3 gamut (wider than sRGB) — available after extend([p3])
inGamutP3('oklch(0.64 0.27 29)'); // true — inside P3 but outside sRGB
inGamutP3('oklch(0.5 0.4 180)'); // false — outside P3
Colordx.toGamutP3('oklch(0.5 0.4 180)'); // → Colordx at the P3 boundary
// Rec.2020 gamut (wider than P3) — available after extend([rec2020])
inGamutRec2020('oklch(0.5 0.4 180)'); // false — outside Rec.2020
Colordx.toGamutRec2020('oklch(0.5 0.4 180)'); // → Colordx at the Rec.2020 boundaryGamut containment is hierarchical: sRGB ⊂ Display-P3 ⊂ Rec.2020. 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.
Opt-in plugins for less common color spaces and utilities:
import { extend } from '@colordx/core';
import a11y from '@colordx/core/plugins/a11y';
// isReadable(), readableScore(), minReadable(), apcaContrast(), isReadableApca()
import cmyk from '@colordx/core/plugins/cmyk';
// toCmyk(), toCmykString(), parses device-cmyk() strings and CMYK objects
import harmonies from '@colordx/core/plugins/harmonies';
// harmonies()
import hwb from '@colordx/core/plugins/hwb';
// toHwb(), toHwbString(), parses hwb() strings and HWB objects
import hsv from '@colordx/core/plugins/hsv';
// toHsv(), toHsvString(), parses hsv() strings and HSV objects
import lab from '@colordx/core/plugins/lab';
// toLab(), toLabString(), toXyz(), toXyzString(), mixLab(), delta(), parses Lab/XYZ objects
import lch from '@colordx/core/plugins/lch';
// toLch(), toLchString(), parses lch() strings and LCH objects
import minify from '@colordx/core/plugins/minify';
// minify() — shortest CSS string
import mix from '@colordx/core/plugins/mix';
// tints(), shades(), tones(), palette()
import names from '@colordx/core/plugins/names';
// toName(), parses CSS color names
import p3 from '@colordx/core/plugins/p3';
// toP3(), toP3String(), inGamutP3(), Colordx.toGamutP3(), linearToP3Channels(), oklchToP3Channels(), parses color(display-p3 ...) strings
import rec2020 from '@colordx/core/plugins/rec2020';
// toRec2020(), toRec2020String(), inGamutRec2020(), Colordx.toGamutRec2020(), linearToRec2020Channels(), oklchToRec2020Channels(), parses color(rec2020 ...) strings
extend([lab, lch, cmyk, names, a11y, harmonies, hwb, hsv, mix, minify, p3, rec2020]);CIE Lab (D50) and CIE XYZ (D50) color models. Lab and XYZ objects are also accepted as color input (Lab requires a colorSpace: 'lab' discriminant). Also adds .mixLab() for colord-compatible perceptual mixing, .delta() for CIEDE2000 color difference, and string conversion methods.
import lab from '@colordx/core/plugins/lab';
extend([lab]);
colordx('#ff0000').toLab(); // { l: 54.29, a: 80.8, b: 69.89, alpha: 1, colorSpace: 'lab' }
colordx('#ff0000').toLabString(); // 'lab(54.29% 80.8 69.89)'
colordx('lab(54.29% 80.8 69.89)').toHex(); // '#ff0000' — lab strings are parseable
colordx('#ff0000').toXyz(); // { x: 43.61, y: 22.25, z: 1.39, alpha: 1 }
colordx('#ff0000').toXyzString(); // 'color(xyz-d65 43.61 22.25 1.39)'
// Lab and XYZ objects parse as color input (with lab plugin loaded)
// Lab objects require colorSpace: 'lab' to distinguish from OKLab (which has the same l/a/b shape)
colordx({ l: 54.29, a: 80.8, b: 69.89, alpha: 1, colorSpace: 'lab' as const }).toHex(); // '#ff0000'
colordx({ x: 43.61, y: 22.25, z: 1.39, alpha: 1 }).toHex(); // '#ff0000'
// Mix in CIE Lab space (colord-compatible)
colordx('#000000').mixLab('#ffffff').toHex(); // '#777777'
// CIEDE2000 perceptual color difference (0 = identical, ~1 = maximum)
colordx('#ff0000').delta('#ff0000'); // 0
colordx('#000000').delta('#ffffff'); // ~1
colordx('#ff0000').delta(); // compared against white (default)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(); // { l: 54.29, c: 106.84, h: 40.86, alpha: 1, colorSpace: 'lch' }
colordx('#ff0000').toLchString(); // 'lch(54.29% 106.84 40.86)'
colordx('lch(54.29% 106.84 40.86)').toHex(); // '#ff0000'
// LCH objects require colorSpace: 'lch' to distinguish from OKLCH (which has the same l/c/h shape)
colordx({ l: 50, c: 50, h: 180, alpha: 1, colorSpace: 'lch' as const }).toHex(); // parses as LCH objectCMYK color model. Parses device-cmyk() CSS strings and CMYK objects.
import cmyk from '@colordx/core/plugins/cmyk';
extend([cmyk]);
colordx('#ff0000').toCmyk(); // { c: 0, m: 100, y: 100, k: 0, alpha: 1 }
colordx('#ff0000').toCmykString(); // 'device-cmyk(0% 100% 100% 0%)'
colordx('device-cmyk(0% 100% 100% 0%)').toHex(); // '#ff0000'
colordx({ c: 0, m: 100, y: 100, k: 0, alpha: 1 }).toHex(); // '#ff0000'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(); // '#ff0000'
colordx('rebeccapurple').toHex(); // '#663399'
colordx('#ff0000').toName(); // 'red'
colordx('#c06060').toName(); // undefined — no CSS name for this color
colordx('#c06060').toName({ closest: true }); // nearest named color by RGB distanceHSV/HSVa color model. Parses hsv() / hsva() strings and HSV objects.
import hsv from '@colordx/core/plugins/hsv';
extend([hsv]);
colordx('#ff0000').toHsv(); // { h: 0, s: 100, v: 100, alpha: 1 }
colordx('#ff0000').toHsvString(); // 'hsv(0, 100%, 100%)'
colordx('hsv(0, 100%, 100%)').toHex(); // '#ff0000'
colordx({ h: 0, s: 100, v: 100, alpha: 1 }).toHex(); // '#ff0000'Color harmony generation using hue rotation.
import harmonies from '@colordx/core/plugins/harmonies';
extend([harmonies]);
colordx('#ff0000').harmonies(); // complementary (default) — 2 colors
colordx('#ff0000').harmonies('complementary'); // [0°, 180°] — 2 colors
colordx('#ff0000').harmonies('analogous'); // [−30°, 0°, 30°] — 3 colors
colordx('#ff0000').harmonies('split-complementary'); // [0°, 150°, 210°] — 3 colors
colordx('#ff0000').harmonies('triadic'); // [0°, 120°, 240°] — 3 colors
colordx('#ff0000').harmonies('tetradic'); // [0°, 90°, 180°, 270°] — 4 colors (square)
colordx('#ff0000').harmonies('rectangle'); // [0°, 60°, 180°, 240°] — 4 colors
colordx('#ff0000').harmonies('double-split-complementary'); // [−30°, 0°, 30°, 150°, 210°] — 5 colorsCSS Color Level 4 HWB (Hue, Whiteness, Blackness) color model.
import hwb from '@colordx/core/plugins/hwb';
extend([hwb]);
colordx('#ff0000').toHwb(); // { h: 0, w: 0, b: 0, alpha: 1 }
colordx('#ff0000').toHwbString(); // 'hwb(0 0% 0%)'
colordx('hwb(0 0% 0%)').toHex(); // '#ff0000'
colordx({ h: 0, w: 0, b: 0, alpha: 1 }).toHex(); // '#ff0000'
// toHwb accepts an optional precision argument (decimal places):
colordx('#3d7a9f').toHwb(); // { h: 203, w: 24, b: 38, alpha: 1 } — default (0)
colordx('#3d7a9f').toHwb(2); // { h: 202.65, w: 23.92, b: 37.65, alpha: 1 }
colordx('#3d7a9f').toHwbString(); // 'hwb(203 24% 38%)'
colordx('#3d7a9f').toHwbString(2); // 'hwb(202.65 23.92% 37.65%)'Color mixing helpers built on top of .mix().
import mix from '@colordx/core/plugins/mix';
extend([mix]);
colordx('#ff0000').tints(5); // [#ff0000, #ff4040, #ff8080, #ffbfbf, #ffffff]
colordx('#ff0000').shades(3); // [#ff0000, #800000, #000000]
colordx('#ff0000').tones(3); // [#ff0000, #c04040, #808080]
// palette: N evenly-spaced stops toward any target (default: white)
colordx('#ff0000').palette(3, '#0000ff'); // [#ff0000, #800080, #0000ff]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(); // '#f00'
colordx('#ffffff').minify(); // '#fff'
colordx('#ff0000').minify({ name: true }); // 'red' — requires names plugin
colordx({ r: 0, g: 0, b: 0, a: 0 }).minify({ transparent: true }); // 'transparent'
colordx({ r: 255, g: 0, b: 0, a: 0.5 }).minify({ alphaHex: true }); // '#ff000080'
// Disable specific formats to exclude them from candidates:
colordx('#ff0000').minify({ hsl: false }); // skips HSL, picks from hex/RGBWCAG 2.x contrast:
colordx('#000').isReadable('#fff'); // true — AA normal (ratio >= 4.5)
colordx('#000').isReadable('#fff', { level: 'AAA' }); // true — AAA normal (ratio >= 7)
colordx('#000').isReadable('#fff', { size: 'large' }); // true — AA large (ratio >= 3)
colordx('#000').readableScore('#fff'); // 'AAA'
colordx('#e60000').readableScore('#ffff47'); // 'AA'
colordx('#949494').readableScore('#fff'); // 'AA large'
colordx('#aaa').readableScore('#fff'); // 'fail'
colordx('#777').minReadable('#fff'); // darkened/lightened to reach 4.5APCA (Accessible Perceptual Contrast Algorithm) — the projected replacement for WCAG 2.x in WCAG 3.0:
// Returns a signed Lc value: positive = dark text on light bg, negative = light text on dark bg
colordx('#000').apcaContrast('#fff'); // 106.0
colordx('#fff').apcaContrast('#000'); // -107.9
colordx('#202122').apcaContrast('#cf674a'); // 37.2 ← dark text on orange
colordx('#ffffff').apcaContrast('#cf674a'); // -69.5 ← white text on orange
// Checks readability using |Lc| thresholds: >= 75 for normal text, >= 60 for large text/headings
colordx('#000').isReadableApca('#fff'); // true
colordx('#777').isReadableApca('#fff'); // false
colordx('#777').isReadableApca('#fff', { size: 'large' }); // trueAPCA is better suited than WCAG 2.x for dark color pairs and more accurately reflects human perception. See Introduction to APCA for background.
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(); // { r: 0.9175, g: 0.2003, b: 0.1386, alpha: 1, colorSpace: 'display-p3' }
colordx('#ff0000').toP3String(); // 'color(display-p3 0.9175 0.2003 0.1386)'
// Parse Display-P3 strings (alpha optional)
colordx('color(display-p3 0.9175 0.2003 0.1386)').toHex(); // '#ff0000'
colordx('color(display-p3 0.9175 0.2003 0.1386 / 0.5)').toHex(); // '#ff000080'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)'); // true — inside P3 but outside sRGB
Colordx.toGamutP3('oklch(0.5 0.4 180)'); // → Colordx at the P3 boundary
oklchToP3Channels(0.5, 0.2, 240); // [r, g, b] gamma-encoded P3 in [0, 1]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();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(); // { r: 0.792, g: 0.231, b: 0.0738, alpha: 1, colorSpace: 'rec2020' }
colordx('#ff0000').toRec2020String(); // 'color(rec2020 0.792 0.231 0.0738)'
// Parse Rec.2020 strings (alpha optional)
colordx('color(rec2020 0.792 0.231 0.0738)').toHex(); // '#ff0000'
colordx('color(rec2020 0.792 0.231 0.0738 / 0.5)').toHex(); // '#ff000080'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)'); // false — outside Rec.2020
Colordx.toGamutRec2020('oklch(0.5 0.4 180)'); // → Colordx at the Rec.2020 boundary
oklchToRec2020Channels(0.5, 0.2, 240); // [r, g, b] gamma-encoded Rec.2020 in [0, 1]Object parsing is also supported using the colorSpace discriminant:
colordx({ r: 0.7919, g: 0.2307, b: 0.0739, alpha: 1, colorSpace: 'rec2020' }).toHex();The API is intentionally compatible. Most code works unchanged:
// Before
import { colord } from 'colord';
const c = colord('#ff0000');
// After
import { colordx } from '@colordx/core';
const c = colordx('#ff0000');All core manipulation and conversion methods have identical signatures:
.toHex(), .toRgb(), .toRgbString(), .toHsl(), .toHslString(), .lighten(), .darken(), .saturate(), .desaturate(), .grayscale(), .invert(), .rotate(), .alpha(), .hue(), .brightness(), .isDark(), .isLight(), .isEqual(), getFormat(), random()
The following remain plugin-only (same as colord): .mix(), .mixOklab(), .luminance(), .contrast(), .toHwb(), .toHwbString().
.lighten(), .darken(), .saturate(), and .desaturate() accept an optional { relative: true } flag not present in colord — see Relative lighten/darken below.
HSV moved to a plugin:
// colord
colord('#ff0000').toHsv();
// colordx
import hsv from '@colordx/core/plugins/hsv';
extend([hsv]);
colordx('#ff0000').toHsv();OKLCH and OKLab are now core — no plugin needed:
// colord (requires plugin — not available)
// colordx
colordx('#ff0000').toOklch();
colordx('#ff0000').toOklchString();
colordx('oklch(0.5 0.2 240)').toHex();CIE Lab, LCH, XYZ, CMYK moved to plugins:
// colord
import { colord, extend } from 'colord';
import labPlugin from 'colord/plugins/lab';
import lchPlugin from 'colord/plugins/lch';
import xyzPlugin from 'colord/plugins/xyz';
import cmykPlugin from 'colord/plugins/cmyk';
extend([labPlugin, lchPlugin, xyzPlugin, cmykPlugin]);
// colordx
import { colordx, extend } from '@colordx/core';
import lab from '@colordx/core/plugins/lab';
import lch from '@colordx/core/plugins/lch';
import cmyk from '@colordx/core/plugins/cmyk';
// Note: XYZ is part of the lab plugin in colordx
extend([lab, lch, cmyk]);getFormat() import path:
// colord
import { getFormat } from 'colord';
// colordx
import { getFormat } from '@colordx/core';Alpha channel property renamed from a to alpha:
colord used a as the alpha key in all color objects. colordx uses alpha everywhere (except OKLab and CIE Lab where a is a color axis).
// colord
colord('#ff0000').toRgb(); // { r: 255, g: 0, b: 0, a: 1 }
colord({ r: 255, g: 0, b: 0, a: 1 });
// colordx
colordx('#ff0000').toRgb(); // { r: 255, g: 0, b: 0, alpha: 1 }
colordx({ r: 255, g: 0, b: 0, alpha: 1 });colord's mix plugin interpolated in CIE Lab space. colordx's mix() uses sRGB interpolation, matching CSS color-mix(in srgb, ...) and how browsers composite layers.
colordx('#000000').mix('#ffffff').toHex(); // '#808080' — sRGB (CSS spec)
colordx('#000000').mixOklab('#ffffff').toHex(); // '#636363' — Oklab (perceptually uniform)
// colord-compatible Lab mixing — requires lab plugin
import lab from '@colordx/core/plugins/lab';
extend([lab]);
colordx('#000000').mixLab('#ffffff').toHex(); // '#777777' — CIE Lab (colord-compatible)The same applies to tints(), shades(), and tones() from the mix plugin, which all call .mix() internally. If you have hardcoded expected hex values from colord's mix output, switch to .mixLab() or update the values.
colord uses Math.floor when rounding the WCAG contrast ratio to 2 decimal places; colordx uses standard rounding (Math.round). This affects values that fall exactly at .xxx5:
colord('#ff0000').contrast('#ffffff'); // 3.99 (floor)
colordx('#ff0000').contrast('#ffffff'); // 4 (round)colordx returns higher precision HSL/HSV values than colord. If your code does exact equality checks on .toHsl() output, use toBeCloseTo or round the values.
toHsl() now accepts an optional precision argument to control decimal places:
colordx('#3d7a9f').toHsl(); // { h: 202.65, s: 44.55, l: 43.14, alpha: 1 } — default (2)
colordx('#3d7a9f').toHsl(4); // { h: 202.6531, s: 44.5455, l: 43.1373, alpha: 1 }
colordx('#3d7a9f').toHsl(0); // { h: 203, s: 45, l: 43, alpha: 1 }The minify() plugin preserves full HSL precision when building candidates, so minification is now lossless — it only picks HSL when the string is genuinely shorter than hex/rgb.
By default, .lighten(0.1) shifts lightness by an absolute 10 percentage points (same as colord). 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:
// Color with l=10%
colordx('#1a0000').lighten(0.1); // l = 10 + 10 = 20% (absolute)
colordx('#1a0000').lighten(0.1, { relative: true }); // l = 10 * 1.1 = 11% (relative)
// Color with s=40%
colordx('#a35050').saturate(0.1); // s = 40 + 10 = 50% (absolute)
colordx('#a35050').saturate(0.1, { relative: true }); // s = 40 * 1.1 = 44% (relative)The same flag works on .darken() and .desaturate().
color-mix()— parse and evaluatecolor-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 ...),color(xyz-d50 ...),color(xyz-d65 ...)string parsing (display-p3andrec2020already supported)- Relative color syntax —
oklch(from red l c h)and channel arithmetic likeoklch(from red l calc(c + 0.1) h)
- Deduplicate the sRGB→XYZ D65 matrix shared between
xyz.tsandlab.ts
MIT