mrkeyoor.com_
Sat 08 Aug 22:52 UTC
npmUtilsupdated 08 Aug 2026

chroma-js

Chroma.js is a zero-dependency color conversion and palette toolkit for JavaScript. It parses CSS colors and numeric channel formats, converts among RGB, HSL, Lab, Lch, OKLab, OKLch, CMYK, temperature, and other spaces, then formats the result for CSS or data output. Its more distinctive half is scale generation: continuous gradients, discrete classes, ColorBrewer palettes, perceptual interpolation, lightness correction, data limits, contrast, and color-difference calculations. It is designed for visualization and color computation, not for styling components or managing design tokens.

Verdict

Chroma.js remains an excellent practical choice for data visualization and palette work, especially when scales matter as much as conversion. For simple UI color edits use a narrower package, and wait for a post-3.2.0 release before relying on the ESM light entry.

API stability3/5Core calls such as `chroma(color)`, conversions, scales, `mix`, and `contrast` have years of continuity, but the changelog records meaningful observable changes. Version 3 switched CSS serialization to modern syntax and replaced the OKLab implementation; version 3.2 changed the no-argument result of `scale.domain()`. Alpha-aware `.hex()` also changed in an earlier line. These are defensible fixes, yet color strings and scale domains often feed snapshots or persisted output, so upgrades need behavioral tests rather than trust in unchanged method names.
Docs4/5The live documentation demonstrates input formats, conversions, manipulation, scales, classes, interpolation modes, analysis, contrast, Delta E, ColorBrewer, and generators with executable-looking examples. A shipped changelog identifies major behavioral breaks. Some operational details remain too quiet: the package does not explain the missing 3.2.0 ESM light file, TypeScript support is not first-party, APCA's beta warning lives in source, and the README still shows a compact quantile-domain example that deserves comparison with the current `limits` and `classes` APIs.
Maintenance4/5The repository is unarchived, was pushed in June 2026, and released 3.2.0 in November 2025 after several 3.1 releases and a 2024 major. Recent work fixed multi-stop domains, missing package files, deep ESM imports, modern CSS parsing, and hue-less Lch interpolation. GitHub reports 76 open issues and PRs, and the README explicitly says maintenance is intermittent rather than weekly. That cadence is acceptable for mature color math, but packaging and edge-case fixes may wait between releases.
Ecosystem5/5The npm endpoint reports 3,155,619 downloads for July 31 through August 6, 2026, and the repository has 10,578 stars. Chroma.js is widely recognizable in charting and mapping work, bundles ColorBrewer palettes, supports browser and Node.js consumers, and covers CSS plus visualization-specific color spaces in one API. Community TypeScript declarations and many examples exist around it. The main ecosystem blemish is that typing remains outside the package and the lighter export is currently inconsistent between module systems.

Use it if

  • You build charts or maps and need data domains, class breaks, ColorBrewer palettes, and perceptual interpolation in one package
  • You need to parse and convert both legacy and modern CSS color forms, including Lab, Lch, OKLab, and OKLch
  • You want color utilities such as WCAG contrast, APCA estimates, Delta E 2000, blending, averaging, or color-temperature conversion
  • You prefer one mature, zero-dependency color toolkit over assembling several single-purpose conversion packages
Skip it if

Setup reality

For JavaScript, setup starts and mostly ends with `npm install chroma-js` and `import chroma from 'chroma-js'`. There are no runtime dependencies, peers, native builds, credentials, or configuration files. The full ESM entry and CommonJS build are both exported. The rough edges appear when you optimize or add types. The full build is 16.2 KB gzipped according to Bundlephobia, and version 3.2.0 advertises `chroma-js/light`; however, its ESM target `index-light.js` is absent from the published tarball. `require('chroma-js/light')` has a CommonJS target, but an ESM import fails until a release includes the repository's later missing-file fix. Deep ESM imports under documented `src` paths are exported, but they are lower-level and easy to misuse because much of Chroma's API is installed through module side effects. TypeScript declarations are not bundled, so use separately versioned community declarations or write a small local type boundary and check it against v3. The other migration surprise is output: `color.css()` now emits modern space-separated CSS syntax. Alpha-bearing `.hex()` values default to eight-digit RGBA, so request `.hex('rgb')` when a six-digit result is required. Color methods generally return new values, but scales are callable objects with mutable configuration methods and an internal cache. Decide your interpolation space explicitly, validate untrusted strings with `chroma.valid()`, treat `null` as the scale's no-data value, and add tests for clipped colors, hue-less interpolation, white-point changes, and exact serialized output.

Patterns

Parse a CSS color and choose an output formatparse-and-format

import chroma from 'chroma-js';

const color = chroma('rgba(255, 0, 0, 0.5)');
console.log(color.css());       // rgb(255 0 0 / 0.5)
console.log(color.hex());       // #ff000080
console.log(color.hex('rgb'));  // #ff0000
console.log(color.rgba());      // [255, 0, 0, 0.5]

Version 3 emits modern CSS syntax, and `.hex()` includes alpha automatically when opacity is below 1. Request `rgb` for a six-digit hex value.

Validate untrusted color text before parsingvalidate-input

import chroma from 'chroma-js';

function normalizeColor(input) {
  if (!chroma.valid(input)) return null;
  return chroma(input).hex();
}

console.log(normalizeColor('oklch(70% 0.1 30)'));
console.log(normalizeColor('not-a-color')); // null

Calling `chroma(input)` directly can throw for an invalid color. `chroma.valid()` is the intended guard for user-provided values.

Convert between RGB and perceptual spacesconvert-color-spaces

import chroma from 'chroma-js';

const color = chroma('#663399');
const rgb = color.rgb();
const lab = color.lab();
const oklch = color.oklch();

const rebuilt = chroma.oklch(...oklch);
console.log({ rgb, lab, oklch, hex: rebuilt.hex() });

Conversions can leave the displayable RGB gamut. Check `.clipped()` after constructing or editing perceptual channels when exact displayability matters.

Adjust lightness, saturation, and opacityadjust-color

import chroma from 'chroma-js';

const base = chroma('#6699cc');
const active = base.darken(0.6).saturate(0.4).alpha(0.85);
const disabled = base.desaturate(1.2).alpha(0.5);

console.log(active.css());
console.log(disabled.css());
console.log(base.hex()); // original is unchanged

Color adjustment methods return new color objects. Their numeric amounts are library units, not CSS percentages.

Interpolate two colors in a chosen spacemix-perceptually

import chroma from 'chroma-js';

const midpoint = chroma.mix('#ff0000', '#0000ff', 0.5, 'oklch');
const quarter = chroma.mix('#ff0000', '#0000ff', 0.25, 'lab');

console.log(midpoint.hex());
console.log(quarter.hex());

Interpolation mode materially changes the result. Set it explicitly for design or visualization work instead of depending on the default.

Map a numeric domain to a continuous scalemap-numeric-domain

import chroma from 'chroma-js';

const temperature = chroma
  .scale(['#2166ac', '#f7f7f7', '#b2182b'])
  .domain([-20, 0, 40])
  .mode('lab');

console.log(temperature(-8).hex());
console.log(temperature(25).hex());
console.log(temperature.domain()); // [-20, 0, 40] in 3.2.0

Version 3.2 changed no-argument `.domain()` to return the original domain array, including intermediate stops, rather than only its endpoints.

Create quantile classes from databuild-classed-scale

import chroma from 'chroma-js';

const values = [3, 4, 4, 8, 12, 19, 21, 80, 110];
const breaks = chroma.limits(values, 'q', 5);
const scale = chroma.scale('YlGnBu').classes(breaks);

const rows = values.map((value) => ({ value, color: scale(value).hex() }));
console.log(breaks, rows);

Quantile classes distribute observations, not numeric distance. Duplicate-heavy data can produce repeated breaks, so inspect the returned array before publishing a legend.

Generate a fixed palette with corrected lightnesssample-palette

import chroma from 'chroma-js';

const palette = chroma
  .scale(['#440154', '#21918c', '#fde725'])
  .mode('lab')
  .correctLightness()
  .colors(7);

console.log(palette);

`.correctLightness()` changes sample positions to make perceived lightness more regular. It is useful for sequential ramps, not automatically appropriate for diverging scales.

Use a built-in ColorBrewer paletteuse-colorbrewer

import chroma from 'chroma-js';

const paletteName = 'RdYlBu';
if (!(paletteName in chroma.brewer)) throw new Error('unknown palette');

const scale = chroma.scale(paletteName).domain([0, 100]);
console.log(scale.colors(9));

Chroma's interpolated output is not always identical to an official ColorBrewer class count. Use the exact `chroma.brewer[name]` array when official discrete swatches are required.

Calculate text and background contrastcheck-contrast

import chroma from 'chroma-js';

const foreground = '#222222';
const background = '#ffffff';
const wcagRatio = chroma.contrast(foreground, background);
const apcaEstimate = chroma.contrastAPCA(foreground, background);

console.log({ wcagRatio, apcaEstimate });

The source labels APCA support beta and warns that its algorithm may change. Keep WCAG and APCA results distinct and do not treat their numeric scales as interchangeable.

Measure perceptual color differencemeasure-color-difference

import chroma from 'chroma-js';

const expected = '#ff0000';
const measured = '#fe0100';
const deltaE2000 = chroma.deltaE(expected, measured);
const labDistance = chroma.distance(expected, measured, 'lab');

console.log({ deltaE2000, labDistance });

`deltaE()` uses Delta E 2000, while `distance()` is Euclidean distance in the selected color space. Their values answer different questions.

Give missing values an explicit scale colorhandle-missing-data

import chroma from 'chroma-js';

const scoreColor = chroma
  .scale(['#f7fbff', '#08306b'])
  .domain([0, 100])
  .nodata('#bdbdbd');

console.log(scoreColor(72).hex());
console.log(scoreColor(null).hex());      // #bdbdbd
console.log(scoreColor(undefined).hex()); // #bdbdbd

Scales treat `null` and non-numeric values as no data. Set `.nodata()` deliberately so missing values cannot masquerade as the low end of a ramp.

Alternatives

PackageRegistryPick it when
culorinpmChoose it for a more modular, modern color-space toolkit with tree-shakable functions and broad CSS Color support
colornpmChoose it for straightforward immutable CSS color parsing, conversion, and adjustment without Chroma's data-scale machinery
tinycolor2npmChoose it for familiar parsing and basic manipulation in older browser-oriented projects that do not need perceptual scales