mrkeyoor.com_
Sat 08 Aug 22:01 UTC
npmWeb Frontendupdated 08 Aug 2026

moo-color

moo-color 2 is an immutable TypeScript class for parsing, converting, formatting, and adjusting colors. It accepts hex, named colors, transparent, RGB, HSL, HWB, HSV, and CMYK input; emits several string and data forms; and includes mixing, hue and lightness changes, random HWB colors, WCAG 2.1 luminance, and contrast ratios. It ships ESM, CommonJS, browser-global, and declaration builds.

Verdict

moo-color 2 is a pleasant small immutable utility for conventional RGB, HSL, HWB, HSV, and CMYK work. Pick Culori when modern CSS color spaces or deeper color science are part of the requirement.

API stability3/5The project states that it follows semantic versioning and version 2 presents a coherent immutable surface, but 2.0.0 intentionally reversed the mutation contract of every manipulation method, changed setAlpha and changeModel, removed setColor, renamed RandomArguments, and raised the Node floor to 18. Those changes are properly documented, yet upgrading from 1.x requires a real code audit rather than a package.json edit.
Docs5/5The rewritten v2 documentation covers accepted strings, data types, static methods, accessors, every formatter and manipulation, immutability, and runnable ESM, CommonJS, and browser examples. The changelog lists breaking changes precisely. The main gaps are operational caveats: alpha is not composited for contrast, string types are intentionally loose, and unsupported modern CSS forms are not listed in one explicit section.
Maintenance5/5Version 2.0.0 shipped in April 2026 with a TypeScript rewrite, immutable behavior, corrected WCAG luminance, new build tooling, current CI across Node 18, 20, and 22, and rewritten documentation. The repository was pushed again in June 2026, is not archived, and currently reports only 3 open issues and pull requests. For a one-maintainer utility with 4 stars, the current maintenance evidence is unusually strong.
Ecosystem3/5moo-color recorded 3,224,027 downloads in the measured week, publishes ESM, CommonJS, IIFE, and declaration builds, and depends on the established color-name table. GitHub has only 4 stars, and there are no framework adapters, scale tools, color-space plugins, or documented extension points. Much of its visibility appears transitive, so community examples are far scarcer than for color, chroma-js, or Culori.

Use it if

  • You want one immutable class for common color parsing, conversion, formatting, and adjustment tasks
  • You need WCAG 2.1 relative luminance and the 4.5-to-1 normal-text AA check without a larger accessibility toolkit
  • You consume both CSS colors and non-CSS working models such as HSV or CMYK
  • You need ESM, CommonJS, a direct browser script build, and bundled TypeScript declarations from the same package
Skip it if

Setup reality

Install moo-color and import the named MooColor class; a default export also exists, and CommonJS uses const { MooColor } = require('moo-color'). Version 2 requires Node 18 or newer, has one dependency on color-name, and needs no native build, peer package, config, or credentials. Browser users can load dist/moo-color.global.js and receive window.MooColor, but a normal bundler should use the package export. The migration trap is v2 immutability. color.lighten(20), color.setAlpha(0.5), and color.changeModel('hsl') no longer change color; save the returned instance or chain it. setColor was removed, so replace it with new MooColor(nextValue). Invalid strings throw from the constructor rather than producing an invalid-state object, and the broad TypeScript input type does not remove the need for try/catch around user input. The supported parser list does not cover newer Lab and OKLCH syntax, and examples use comma-separated functional forms, so test any CSS syntax copied directly from modern stylesheets. Contrast helpers implement the normal-text AA threshold of 4.5 only. They do not expose the 3-to-1 large-text threshold or 7-to-1 AAA threshold, and they ignore alpha compositing; first blend translucent foregrounds against the actual background. Values are converted through RGB for most model pairs, so repeated cross-model edits can accumulate rounding differences. Formatting chooses rgba, hsla, or hex alpha automatically when opacity is below one.

Patterns

Parse a user-provided color safelyparse-user-color

import { MooColor } from 'moo-color'

function parseColor(input) {
  try {
    return new MooColor(input)
  } catch {
    return null
  }
}

The constructor throws for an unrecognized string; TypeScript's ColorInput type still permits arbitrary named-color strings.

Format one color several waysformat-color

const color = new MooColor('rgba(255, 128, 0, 0.5)')

console.log(color.toHex())
console.log(color.toRgb('percent'))
console.log(color.toHsl())
console.log(color.toHwb())

Opacity is preserved and causes alpha-bearing output such as eight-digit hex, rgba, or hsla.

Prefer short hex or an exact CSS nameuse-short-or-named-hex

const red = new MooColor('#ff0000')

red.toHex('short') // '#f00'
red.toHex('name')  // 'red'

name mode only returns a name for an exact opaque table match; otherwise it falls back to hexadecimal.

Chain immutable color adjustmentschain-immutable-edits

const base = new MooColor('hsl(210, 60%, 40%)')
const accent = base
  .lighten(15)
  .saturate(10)
  .rotate(20)

console.log(base.toHsl())
console.log(accent.toHsl())

Version 2 returns a new instance at every step. The base value remains unchanged.

Retain the result of a v2 manipulationmigrate-v1-mutation

let color = new MooColor('#336699')
color = color.lighten(10)
color = color.setAlpha(0.8)

Calling these methods without assignment was useful in v1 but silently leaves the original unchanged in v2.

Read converted numeric color datainspect-color-data

const color = new MooColor('#ff8000')
const hsl = color.getColorAs('hsl')

console.log(hsl.model, hsl.values, hsl.alpha)

getColor and getColorAs return copies, so changing the values array does not mutate the MooColor instance.

Create an instance stored in another modelchange-working-model

const rgb = new MooColor('#663399')
const hwb = rgb.changeModel('hwb')

console.log(rgb.getModel()) // 'rgb'
console.log(hwb.getModel()) // 'hwb'

changeModel is immutable in v2. Most conversions pass through RGB, except direct HSV and HWB conversion.

Check the normal-text AA thresholdcheck-wcag-contrast

const foreground = new MooColor('#1f2937')
const background = new MooColor('#ffffff')

const ratio = foreground.contrastRatioWith(background)
const passesNormalAA = foreground.isContrastEnough(background)

isContrastEnough uses a fixed 4.5 threshold. Apply 3 for qualifying large text or 7 for AAA yourself.

Composite transparency before a contrast checkhandle-alpha-contrast

const page = new MooColor('#ffffff')
const translucent = new MooColor('rgba(0, 0, 0, 0.5)')
const effective = page.mix(translucent, translucent.getAlpha() * 100)

console.log(effective.contrastRatioWith(page))

contrastRatioWith ignores alpha. This blend approximates the visible foreground on an opaque page before calculating contrast.

Blend two colors with explicit weightingmix-two-colors

const red = new MooColor('red')
const blue = new MooColor('blue')

const mostlyRed = MooColor.mix(red, blue, 75)
const mostlyBlue = red.mix(blue, 75)

Static mix weights its first color; instance mix weights the color passed as the argument. The percent meanings are intentionally different.

Generate a constrained random colorcreate-random-color

const warmTint = MooColor.random({
  hue: [0, 60],
  white: [10, 30],
  black: 5
})

console.log(warmTint.toHex())

Random generation operates in HWB. Each option accepts a fixed number or an inclusive-looking min and max tuple.

Load version 2 from CommonJSuse-commonjs

const { MooColor } = require('moo-color')

const color = new MooColor('cmyk(0%, 100%, 100%, 0%)')
console.log(color.toRgb())

Version 2 publishes a dedicated CommonJS file and declarations alongside its ESM package. Node 18 or newer is required.

Alternatives

PackageRegistryPick it when
culorinpmModern color science needs Lab, LCH, OKLab, OKLCH, gamut mapping, interpolation, or a function-based API
chroma-jsnpmData visualization needs scales, domain mapping, interpolation modes, and palette generation
colornpmYou want a mature chainable immutable color object with a broad existing user base
tinycolor2npmLegacy browser code values a long-established permissive parser and tinycolor-compatible API