chroma-js, color, color-convert, color-string, rgb-hex, and tinycolor2 are all npm packages designed to handle color parsing, conversion, manipulation, and formatting in JavaScript applications. They enable developers to work with colors across different formats (e.g., hex, RGB, HSL, HSV, LAB) and perform operations like interpolation, lightening/darkening, and generating color scales. While some focus on low-level format translation (color-convert, rgb-hex, color-string), others provide high-level APIs for design-oriented tasks (chroma-js, tinycolor2, color).
Working with colors in JavaScript seems simple until you need to convert between formats, adjust brightness consistently, or generate accessible palettes. The ecosystem offers several focused tools — each solving different parts of the problem. Let’s cut through the noise and see which package fits which job.
Some libraries give you a full paintbrush set; others hand you individual pigments. Understanding this split is key.
High-level libraries (chroma-js, color, tinycolor2) parse strings, manipulate colors, and output formatted results — all in one flow. They’re meant for direct use in apps.
Low-level utilities (color-convert, color-string, rgb-hex) do one tiny thing well. You typically combine them when building your own color system.
💡 Rule of thumb: If you’re writing app code (not a library), start with a high-level tool. Only drop down to low-level packages if you’re optimizing for size or need very specific behavior.
Let’s say you have a CSS color like "#4a90e2" and need its RGB values. How do the packages handle this?
chroma-jsimport chroma from 'chroma-js';
const rgb = chroma('#4a90e2').rgb(); // [74, 144, 226]
const hex = chroma(74, 144, 226).hex(); // '#4a90e2'
colorimport Color from 'color';
const rgb = Color('#4a90e2').rgb().array(); // [74, 144, 226]
const hex = Color.rgb(74, 144, 226).hex(); // '#4a90e2'
tinycolor2import tinycolor from 'tinycolor2';
const rgb = tinycolor('#4a90e2').toRgb(); // { r: 74, g: 144, b: 226 }
const hex = tinycolor({ r: 74, g: 144, b: 226 }).toHex(); // '4a90e2'
color-stringimport * as colorString from 'color-string';
const rgb = colorString.get.rgb('#4a90e2'); // [74, 144, 226, 1]
const hex = colorString.to.hex([74, 144, 226]); // '#4a90e2'
rgb-heximport rgbHex from 'rgb-hex';
import hexRgb from 'hex-rgb';
const hex = rgbHex(74, 144, 226); // '4a90e2'
const rgb = hexRgb('4a90e2'); // { red: 74, green: 144, blue: 226 }
color-convert⚠️ Note:
color-convertdoes not parse strings. It only converts between numeric arrays.
import convert from 'color-convert';
// You must already have numbers
const lab = convert.rgb.lab(74, 144, 226); // [58.32, -14.57, -35.43]
If your input is a string, color-convert alone won’t help — you’d need to pair it with color-string.
Now let’s adjust a color — say, lighten "#4a90e2" by 20%.
chroma-jschroma('#4a90e2').brighten(1).hex(); // '#7fbaf2' (uses Lab for perceptual accuracy)
colorcolor('#4a90e2').lighten(0.2).hex(); // '#7db7f0' (uses HSL by default)
tinycolor2tinycolor('#4a90e2').lighten(20).toHex(); // '7db7f0' (uses HSL)
Notice the results differ slightly. Why? chroma-js uses CIELAB space for adjustments, which matches human perception better. The others use HSL, which is faster but can shift hues unexpectedly.
💡 For design systems where visual consistency matters (e.g., generating 10 shades of blue),
chroma-jsgives more predictable results.
Need to generate a gradient between two colors? Check contrast ratios? Here’s where the big three diverge.
chroma-js excels at smooth, perceptually uniform gradients:
const scale = chroma.scale(['blue', 'red']).mode('lch');
scale(0.5).hex(); // Midpoint in LCH space: '#c9579d'
color doesn’t support scales natively — you’d interpolate manually:
// Not built-in; requires custom logic
tinycolor2 has basic interpolation:
tinycolor.mix('blue', 'red', 50).toHex(); // '800080' (simple RGB blend)
tinycolor2 includes accessibility helpers:
const bgColor = tinycolor('#4a90e2');
bgColor.isDark(); // true
bgColor.mostReadable(['white', 'black']).toHex(); // 'ffffff'
chroma-js can compute contrast but doesn’t have convenience methods:
chroma.contrast('#4a90e2', 'white'); // 4.57 (ratio)
color has no built-in contrast tools.
Sometimes you’re already handling color logic and just need a tiny converter.
rgb-hex: rgbHex(255, 128, 0) → 'ff8000'color-string: colorString.to.hex([255, 128, 0]) → '#ff8000'color-convert: convert.hsl.rgb(240, 100, 50) → [0, 0, 255]These are useful in performance-sensitive code (e.g., canvas animations) where you avoid object instantiation. But for 95% of UI work, the overhead of high-level libraries is negligible.
What happens with invalid input?
tinycolor2: Very forgiving. tinycolor('invalid').isValid() returns false.color: Throws errors on bad input unless you check .isValid() first.chroma-js: Throws descriptive errors (e.g., "unknown format").If you’re processing user input (e.g., a color picker), tinycolor2’s leniency saves boilerplate.
→ Use chroma-js. Its Lab/LCH support ensures colors behave predictably across lightness adjustments and interpolations — critical for data viz.
→ Use tinycolor2. Its readability checks, WCAG helpers, and robust parsing handle real-world edge cases (like named CSS colors) gracefully.
→ Use color. Its chainable API (Color('#f00').darken(0.3).alpha(0.5).string()) is expressive and easy to maintain.
→ Combine color-string + color-convert. Parse once with color-string, then use color-convert for repeated transformations without object overhead.
→ Use rgb-hex/hex-rgb. No need to pull in a 10KB library for a 300-byte task.
| Package | Best For | String Parsing | Manipulation | Color Spaces | Bundle Impact |
|---|---|---|---|---|---|
chroma-js | Data viz, perceptual accuracy, color scales | ✅ | ✅✅✅ | RGB, HSL, LAB, LCH | Medium |
color | Clean UI code, chainable operations | ✅ | ✅✅ | RGB, HSL, HSV | Medium |
tinycolor2 | Accessibility, legacy support, robust parsing | ✅✅✅ | ✅✅ | RGB, HSL, HSV, HEX | Medium-Large |
color-convert | Raw model conversion (no strings) | ❌ | ❌ | 20+ models | Tiny |
color-string | Parsing/formatting CSS strings | ✅ | ❌ | RGB, HSL, HEX | Tiny |
rgb-hex | RGB ↔ hex only | ❌ (arrays) | ❌ | RGB, HEX | Minimal |
Don’t over-engineer. If you’re just setting a few theme colors, even native CSS variables might suffice. But when you need reliable, cross-format color math in JavaScript, pick the tool that matches your task’s scope — not the one with the most stars.
Choose chroma-js when you need advanced color operations like perceptually uniform interpolation, color scales for data visualization, or harmonies (analogous, complementary). It supports a wide range of color spaces including CIELAB and CIELCh, making it ideal for design systems, charting libraries, or accessibility tools that require precise color math.
Choose color if you want a clean, chainable API for common manipulations (lighten, darken, rotate hue) with good format support and readable code. It’s well-suited for UI libraries where you frequently adjust theme colors based on user preferences or system states, and you value expressive syntax over minimal bundle size.
Choose color-convert only when you need raw, stateless conversion between color models without parsing or string formatting. It’s best used as a building block inside other libraries rather than directly in application code, since it doesn’t handle input validation or string I/O.
Choose color-string if your sole requirement is parsing CSS-style color strings (hex, rgb, hsl) into numeric arrays or vice versa. Avoid using it alone for manipulation — pair it with color-convert if you need both parsing and conversion, but prefer higher-level libraries for most real-world use cases.
Choose rgb-hex only for the narrow task of converting between RGB integer arrays and hex strings. It’s extremely lightweight but offers no other functionality — useful in performance-critical contexts where you’re already handling color logic manually and just need this one utility.
Choose tinycolor2 when you need a battle-tested, feature-complete library with excellent browser support and a rich set of utilities (readability, contrast, random colors, WCAG compliance). It’s particularly strong for dynamic theming, accessibility checks, and legacy projects where stability and broad format support outweigh modern API preferences.
Chroma.js is a tiny small-ish zero-dependency JavaScript library for all kinds of color conversions and color scales.
Install from npm
npm install chroma-js
Import package into project
import chroma from "chroma-js";
Initiate and manipulate colors:
chroma('#D4F880').darken().hex(); // #a1c550
Working with color scales is easy, too:
scale = chroma.scale(['white', 'red']);
scale(0.5).hex(); // #FF7F7F
Lab/Lch interpolation looks better than RGB
chroma.scale(['white', 'red']).mode('lab');
Custom domains! Quantiles! Color Brewer!!
chroma.scale('RdYlBu').domain(myValues, 7, 'quantiles');
And why not use logarithmic color scales once in your life?
chroma.scale(['lightyellow', 'navy']).domain([1, 100000], 7, 'log');
Why not dive into the interactive documentation (there's a static version, too). You can download chroma.min.js or use the hosted version on unpkg.com.
You can use it in node.js, too!
npm install chroma-js
Or you can use it in SASS using chromatic-sass!
Come over and say hi in our Discord channel!
First clone the repository and install the dev dependencies:
git clone git@github.com:gka/chroma.js.git
cd chroma.js
npm install
Then compile the coffee-script source files to the build files:
npm run build
Don't forget to tests your changes! You will probably also want to add new test to the /test folder in case you added a feature.
npm test
And to update the documentation just run
npm run docs
To preview the docs locally you can use
npm run docs-preview
Chroma.js is written by Gregor Aisch.
Released under BSD license. Versions prior to 0.4 were released under GPL.
There have been no commits in X weeks. Is chroma.js dead?
No! It's just that the author of this library has other things to do than devoting every week of his life to making cosmetic changes to a piece of software that is working just fine as it is, just so that people like you don't feel like it's abandoned and left alone in this world to die. Bugs will be fixed. Some new things will come at some point. Patience.
I want to help maintaining chroma.js!
Yay, that's awesome! Please say hi at our Discord chat to get in touch