mrkeyoor.com_
Thu 06 Aug 01:02 UTC
npmUtilsupdated 05 Aug 2026

color

color is a small JavaScript library for reading a colour in one format, converting it to another, and nudging it around. You hand the Color() function almost anything a stylesheet would accept, a hex string, an rgb() or hsl() string, a CSS keyword, or a plain object like {r, g, b}, and you get back an immutable object. From there you chain: .lighten(0.2).rotate(45).alpha(0.8) each return a brand new frozen instance, and .hex(), .string(), .array(), or .object() get a value back out. It also carries the WCAG bits people actually need, .luminosity(), .contrast(other), .isDark(), so you can decide whether text on a background should be black or white.

Verdict

Still the most convenient way to take a CSS colour string, convert it, and check contrast, and the immutable v5 API is pleasant. Choose culori or colord instead if you need oklch and wide-gamut CSS, or if you are stuck on CommonJS.

API stability4/5The chainable getter and setter API has looked the same since the 3.x days, but v5 dropped CommonJS entirely and froze instances, which is a real migration rather than a version bump.
Docs3/5The README lists every constructor, getter, and manipulation with inline example output, which is enough to work from. There is no docs site, no explanation of the colour maths, and no warning that oklch strings do not parse.
Maintenance3/5One maintainer, a three-year gap between 4.2.3 in April 2022 and 5.0.0 in February 2025, last push November 2025, and 11 open issues (20 counting PRs). Stable and small rather than actively developed.
Ecosystem5/5Around 50M weekly downloads, mostly transitive, and its sibling packages color-convert, color-string, and color-name sit underneath a large slice of the terminal and styling ecosystem.

Use it if

  • You take colour input as CSS strings from users, a theme file, or a CMS and need to parse hex, rgb(), hsl(), and keywords without writing regexes
  • You need WCAG contrast checks or an isDark test to pick readable text colour over an arbitrary background
  • You build terminal tooling and want rgb converted to ansi16 or ansi256 codes, which the same object handles
  • You want a chainable, immutable API for tweaks such as lighten, darken, rotate, fade, and mix rather than hand-rolling HSL arithmetic
Skip it if

Setup reality

npm install color and that is genuinely it, no peer dependencies and no native build. The friction is packaging. Version 5 declares type: module with a single ./index.js export and Node 18 or newer, so require() fails and any Jest or bundler setup that still transpiles to CommonJS needs ESM handling before your first import works. Version 5 also ships its own index.d.ts, so the separate @types/color package is now redundant and will slowly drift from reality; remove it. Instances are frozen, which means every mutation returns a new object and code that assigned to properties in older versions silently stops working, or throws in strict mode. Invalid input throws rather than returning null, so anything coming from a user needs a try/catch around the constructor.

Patterns

Read one format, write anotherparse-and-convert

import Color from 'color';

const c = Color('#7743CE');
c.rgb().array();       // [ 119, 67, 206 ]
c.hsl().string();      // 'hsl(262.4, 58.6%, 53.5%)'
c.cmyk().round().array(); // [ 42, 67, 0, 19 ]

The object remembers which model it came from, so .object() and .array() reflect the current model, not always rgb. Call .rgb() first if you want rgb output guaranteed.

Handle colour strings you do not controlsafe-parse-user-input

function parseColor(input) {
  try {
    return Color(input);
  } catch {
    return null; // 'Unable to parse color from string'
  }
}

The constructor throws on anything it cannot read, including valid modern CSS such as oklch(0.6 0.2 30). There is no isValid() helper, so try/catch is the only option.

Remember that every call returns a new objectimmutable-chaining

const base = Color('red');
const brighter = base.green(100);

base.hex();     // '#FF0000'  unchanged
brighter.hex(); // '#FF6400'
Object.isFrozen(base); // true

Instances are frozen in v5, so nothing mutates in place and you must keep the return value. Code written against older versions that relied on chaining for side effects quietly does nothing.

Get hex output that keeps transparencyhex-with-alpha

const c = Color('#FF000080');
c.hex();   // '#FF0000'   alpha dropped
c.hexa();  // '#FF000080' 8-digit hex
c.alpha(); // 0.5019607843137255

hex() silently discards alpha, which is the most common source of a colour looking right in dev tools and wrong in the app. Use hexa() or rgb().string() when transparency matters.

Pick readable text colour for a backgroundcontrast-and-readable-text

const bg = Color(userTheme.background);
const text = bg.isLight() ? '#000000' : '#FFFFFF';

const ratio = bg.contrast(Color(text));
if (ratio < 4.5) {
  console.warn(`contrast ${ratio.toFixed(2)} fails WCAG AA for body text`);
}

contrast() returns the WCAG ratio from 1 to 21; AA wants 4.5 for body text and 3 for large text. isLight() uses a YIQ approximation, not the same maths as contrast(), so the two can disagree at the edges.

Build hover and active variantslighten-and-darken

const brand = Color('#2E7D32');
const hover = brand.lighten(0.15).hex();
const active = brand.darken(0.15).hex();

These are multiplicative on HSL lightness, not additive. Color('hsl(100, 50%, 0%)').lighten(0.5) is still 0% lightness, so pure black never lightens; use .lightness(n) to set an absolute value instead.

Blend colours togethermix-two-colors

Color('cyan').mix(Color('yellow')).hex();      // '#80FF80'
Color('cyan').mix(Color('yellow'), 0.3).hex(); // 30% toward yellow

Mixing happens in sRGB, so midpoints of saturated complements come out greyer than a perceptual space would give. For gradients and scales, chroma-js or culori interpolate in lab or oklab.

Work with transparencyalpha-and-fading

const overlay = Color('#000').alpha(0.6).rgb().string();
// 'rgba(0, 0, 0, 0.6)'

Color('rgba(10, 10, 10, 0.8)').fade(0.5).alpha();    // 0.4
Color('rgba(10, 10, 10, 0.8)').opaquer(0.5).alpha(); // 1

alpha(n) sets an absolute value, while fade and opaquer scale the existing one by a ratio, so fade(0.5) on 0.8 gives 0.4, not 0.3. Alpha survives conversion between models.

Control rounding in CSS outputcontrol-string-precision

const c = Color('#7743CE').lighten(0.5);
c.hsl().string();   // one decimal place by default
c.hsl().string(0);  // 'hsl(262, 59%, 80%)'
c.round().object(); // integer channel values

string() takes a decimal-place count and defaults to 1, which is why generated CSS often carries values like 58.6%. Pass 0 when the output goes into a design token file people have to read.

Convert a colour to terminal codesterminal-ansi-codes

Color('#7743CE').ansi256().object(); // { ansi256: 98 }
Color('#7743CE').ansi16().object();  // { ansi16: 95 }

Handy for CLI tools that accept hex in config but must emit ANSI escapes. The mapping is lossy in one direction only: rgb to ansi256 quantises, and converting back gives a different hex.

Know what oklch does and does not do hereoklch-conversion-limits

Color('#ff0000').oklch().round(3).array(); // [ 62.796, 25.768, 29.234 ]
Color('#ff0000').oklch().string();         // 'rgb(255, 0, 0)'
Color('oklch(0.6 0.2 30)');                // throws: unable to parse

The conversion exists through color-convert, but there is no CSS oklch parser and no oklch serializer, so strings round-trip through rgb. If oklch is your working space, use culori.

Read and set individual channelschannel-getters-setters

const c = Color('hsl(200, 50%, 40%)');
c.hue();               // 200
c.saturationl(80).hsl().string();
c.rotate(180).hue();   // 20
c.grayscale().hex();

Calling a channel with no argument reads it, with an argument returns a new colour. hue() wraps modulo 360, so rotate(-90) and rotate(270) are the same, and saturationl versus saturationv is the usual typo.

Alternatives

PackageRegistryPick it when
culorinpmYou need CSS Color 4 spaces such as oklch, display-p3, and proper gamut mapping, with tree-shakeable imports
colordnpmYou want a much smaller immutable library with a similar chainable feel and optional plugins for the extras
chroma-jsnpmYour real job is scales, interpolation, and palettes for data visualisation rather than single-colour tweaks