mrkeyoor.com_
Thu 06 Aug 07:42 UTC
npmWeb Frontendupdated 06 Aug 2026

colord

colord parses color strings and objects, converts between color models, and does the everyday manipulations: lighten, darken, saturate, set alpha, rotate hue, check contrast. The whole thing is one chainable, immutable function, so colord("#ff0000").darken(0.2).alpha(0.5).toRgbString() reads left to right and never mutates the value you started with. Only hex, RGB, HSL and HSV live in the core; CSS color names, WCAG contrast, mixing, LAB, LCH, HWB, CMYK, XYZ and harmonies are opt-in plugins you register once with extend(). That split is why the core stays near 2 KB gzipped with zero dependencies, and it ships its own TypeScript types.

Verdict

The best value per byte for hex, RGB and HSL conversion plus WCAG contrast in a browser bundle, and the immutable chained API is genuinely pleasant. The four-year release gap and the total absence of OKLCH mean it is a poor foundation for a new design-token system.

API stability5/52.9.3 has been the published version since August 2022, so nothing has broken under anyone, and the chainable surface has not changed shape since 2.0. Stability here is partly a side effect of the project standing still.
Docs4/5The README documents every method with copy-pasteable examples and lists each plugin with its own KB cost, and colord.omgovich.ru mirrors it. What is missing is any guidance on the extend() lifecycle, which is the one thing people get wrong.
Maintenance2/5One maintainer, no npm release since August 2022 despite commits landing in May 2026, and 25 open issues (34 counting PRs). The code works today; assume nothing new lands.
Ecosystem4/5About 19.1 million weekly downloads, largely transitive through cssnano and design-system packages, with ten official plugins and types in the box. Third-party plugins are essentially nonexistent.

Use it if

  • You need hex to RGB to HSL conversion plus lighten and darken inside a browser bundle, and you care that it costs about 2 KB gzipped instead of the roughly 16 KB chroma-js adds
  • You want WCAG contrast checks in product code: the a11y plugin gives contrast(), isReadable() with AA and AAA levels, and luminance() without pulling in an accessibility toolkit
  • You want immutability by construction, so a color passed down a component tree cannot be changed under you: every method returns a new Colord instance
  • You are replacing tinycolor2 or color in an existing codebase and want a near-identical method vocabulary with real TypeScript types included in the package
Skip it if

Setup reality

npm install colord and you are done: zero dependencies, ESM and CommonJS builds, TypeScript types in the package, no peer deps and no build step. The friction is entirely the plugin system. Anything outside hex, RGB, HSL and HSV needs a separate import passed to extend() exactly once at application startup, before any call that uses it. Two traps follow from that. First, TypeScript stops complaining the moment you import the plugin module, whether or not extend() actually ran, so a missing extend() surfaces at runtime as "TypeError: colord(...).contrast is not a function". Second, extend() writes onto a shared prototype, so plugin registration is global state: fine in an app entrypoint, wrong in a library. Also note the plugin method values are computed by colord's own ports of the CSS Color 4 conversion code, so LAB and LCH numbers can differ in the second decimal from what the README shows.

Patterns

Parse any supported input and convert between modelsparse-and-convert

import { colord } from "colord";

colord("#ff0000").toRgb();          // { r: 255, g: 0, b: 0, a: 1 }
colord("hsl(0, 100%, 50%)").toHex(); // "#ff0000"
colord({ r: 255, g: 0, b: 0 }).toHslString(); // "hsl(0, 100%, 50%)"
colord("rgba(0, 0, 0, 0.5)").toHsv();

Core input formats are hex (3, 4, 6 and 8 digit), rgb/rgba, hsl/hsla and hsv objects. Named colors like "tomato" need the names plugin; without it they parse as invalid.

Check a color before trusting itvalidate-untrusted-input

import { colord, getFormat } from "colord";

colord("#wwuutt").isValid(); // false
colord("#wwuutt").toHex();   // "#000000"  <- silent fallback
getFormat("#aabbcc");        // "hex"
getFormat("nope");           // undefined

This is the biggest footgun in the library: bad input never throws, it becomes black. Gate every externally sourced color on isValid() or getFormat() before you render it.

Lighten, darken, saturate, grayscaleadjust-lightness-and-saturation

import { colord } from "colord";

colord("#223344").lighten(0.3).toHex(); // "#5580aa"
colord("#5580aa").darken(0.3).toHex(); // "#223344"
colord("#bf4040").saturate(0.25).toHex(); // "#df2020"
colord("#bf4040").grayscale().toHex();    // "#808080"

These operate on HSL lightness and saturation, not perceptual lightness, so lighten(0.3) on a saturated blue and on a yellow do not look equally lighter. Use the lab plugin if you need perceptual results.

Read and set alpha and huealpha-and-hue

import { colord } from "colord";

colord("rgb(0, 0, 0)").alpha(0.5).toRgbString(); // "rgba(0, 0, 0, 0.5)"
colord("rgba(50, 100, 150, 0.5)").alpha();      // 0.5
colord("hsl(90, 50%, 50%)").hue(180).toHslString();
colord("hsl(90, 50%, 50%)").rotate(90).toHslString(); // "hsl(180, 50%, 50%)"

alpha() and hue() are getters with no argument and setters with one. Every setter returns a new instance, so const dark = c.darken(0.2) leaves c untouched.

Register plugins once at the app entrypointregister-plugins

// colord-setup.ts, imported first in your entry file
import { extend } from "colord";
import a11yPlugin from "colord/plugins/a11y";
import namesPlugin from "colord/plugins/names";
import mixPlugin from "colord/plugins/mix";

extend([a11yPlugin, namesPlugin, mixPlugin]);

extend() patches a shared prototype for the whole process, so call it once in application code and never inside a reusable library. Forgetting it gives "TypeError: colord(...).contrast is not a function" even though TypeScript compiled fine.

Check contrast and readability (a11y plugin)check-wcag-contrast

import { colord, extend } from "colord";
import a11yPlugin from "colord/plugins/a11y";
extend([a11yPlugin]);

colord("#777777").contrast();          // 4.47 against white
colord("#000000").isReadable();        // true (AA, normal text)
colord("#e60000").isReadable("#ffff47", { level: "AAA" }); // false
colord("#808080").luminance();         // 0.22

contrast() defaults the second color to #FFF, so always pass the real background. isReadable() defaults to WCAG AA and normal text; pass { size: "large" } for headings.

Convert to and from CSS color keywords (names plugin)css-color-names

import { colord, extend } from "colord";
import namesPlugin from "colord/plugins/names";
extend([namesPlugin]);

colord("tomato").toHex();  // "#ff6347"
colord("#00ffff").toName();// "cyan"
colord("#fe0000").toName();// undefined (not a CSS keyword)
colord("#fe0000").toName({ closest: true }); // "red"

The names plugin is 1.45 KB of the bundle by itself, more than half the core, because it carries the full keyword table. Skip it if you only ever handle hex.

Mix two colors and build tints, shades and tones (mix plugin)mix-and-generate-shades

import { colord, extend } from "colord";
import mixPlugin from "colord/plugins/mix";
extend([mixPlugin]);

colord("#ffffff").mix("#000000").toHex(); // "#777777"
colord("#cd853f").mix("#eee8aa", 0.6).toHex();
colord("#ff0000").tints(3).map((c) => c.toHex());  // ['#ff0000','#ff9f80','#ffffff']
colord("#ff0000").shades(3).map((c) => c.toHex()); // ['#ff0000','#7a1b0b','#000000']

Mixing happens in LAB, not RGB, which is why #ffffff mixed with #000000 lands on #777777 rather than #808080. The returned arrays include the original color as the first element.

Generate a harmony palette from one color (harmonies plugin)generate-harmonies

import { colord, extend } from "colord";
import harmoniesPlugin from "colord/plugins/harmonies";
extend([harmoniesPlugin]);

colord("#ff0000").harmonies("triadic").map((c) => c.toHex());
// ['#ff0000', '#00ff00', '#0000ff']
colord("#ff0000").harmonies("analogous").map((c) => c.toHex());
// ['#ff0080', '#ff0000', '#ff8000']

Accepted types are analogous, complementary, double-split-complementary, rectangle, split-complementary, tetradic and triadic. These are hue rotations in HSL, so the results are a starting point for a designer, not a finished palette.

Compare two colors perceptually (lab and lch plugins)perceptual-difference

import { colord, extend } from "colord";
import labPlugin from "colord/plugins/lab";
import lchPlugin from "colord/plugins/lch";
extend([labPlugin, lchPlugin]);

colord("#3296fa").delta("#197dc8"); // 0.099
colord("#afafaf").delta("#b4b4b4"); // 0.014
colord("#213b0b").toLch();          // { l: 21.92, c: 30.45, h: 125.24, a: 1 }

delta() is Delta E2000 normalized to 0 (identical) through 1 (opposite), so the usual "just noticeable difference" threshold of 1.0 Delta E is roughly 0.01 here. delta() lives in the lab plugin, not a11y.

Emit the shortest valid CSS string (minify plugin)minify-css-output

import { colord, extend } from "colord";
import minifyPlugin from "colord/plugins/minify";
import namesPlugin from "colord/plugins/names";
extend([minifyPlugin, namesPlugin]);

colord("black").minify();                  // "#000"
colord("#112233").minify();                // "#123"
colord("rgba(170,170,170,0.4)").minify();  // "hsla(0,0%,67%,.4)"

minify() will switch notation (hex to hsla) whenever that is shorter, so do not use it where the output format has to stay stable, such as values you diff in snapshot tests.

Use the exported TypeScript color typestyped-color-objects

import { colord, random, RgbColor, HslColor, AnyColor } from "colord";

const brand: HslColor = { h: 210, s: 90, l: 45 };
const accent: RgbColor = { r: 12, g: 90, b: 200 };

function toCss(input: AnyColor): string {
  return colord(input).toHex();
}

random().toHex(); // e.g. "#01c8ec"

Exported types include RgbColor, RgbaColor, HslColor, HslaColor, HsvColor, HsvaColor and AnyColor. They only describe object inputs; a plain string is still just string, so isValid() remains your only runtime check.

Alternatives

PackageRegistryPick it when
culorinpmYou need OKLCH, OKLab, display-p3 or other CSS Color 4 spaces, with per-function imports so tree shaking actually works.
chroma-jsnpmYour job is color scales, gradients, and interpolated palettes rather than one-off conversions, and you can spend roughly 16 KB gzipped.
colorjs.ionpmYou want the spec-tracking implementation written by the CSS Color editors and correctness matters more than bundle size.
tinycolor2npmYou are on an older codebase that already uses it and do not need bundled TypeScript types or the smaller core.