chroma-js, color, polished, and tinycolor2 are essential utilities for handling color logic in JavaScript applications, but they serve distinct architectural roles. chroma-js is a powerhouse for scientific color analysis, offering advanced features like color scales, perceptual uniformity (LCH/Lab), and complex interpolations. color acts as a robust, immutable parser and converter, ideal for strictly validating and transforming color formats without side effects. polished is a CSS-in-JS companion that provides design-system helpers like lighten, darken, and rgba specifically tuned for styled-components and emotion. tinycolor2 is a lightweight, legacy-friendly utility for basic conversions and readability checks, though it lacks modern color space support. Understanding these differences is critical for choosing the right tool for data visualization, dynamic theming, or simple UI adjustments.
Choosing the right color library isn't just about picking a hex code; it's about deciding how your application handles visual logic. Whether you are building a data dashboard, a dynamic theming engine, or a simple UI kit, the underlying math matters. Let's break down chroma-js, color, polished, and tinycolor2 to see how they handle real-world engineering challenges.
When you need to generate a range of colors (for example, mapping data values to colors), the algorithm used for interpolation changes the visual result dramatically. Linear interpolation in RGB often looks muddy, while perceptual spaces like LCH look smooth.
chroma-js is the industry standard for this. It supports sophisticated scales and perceptual color spaces out of the box.
import chroma from 'chroma-js';
// Creates a smooth scale using LCH color space (perceptually uniform)
const scale = chroma.scale(['#000000', '#ffff00', '#ffffff'])
.mode('lch')
.colors(5);
console.log(scale);
// Output: ['#000000', '#4a2b00', '#9f5c00', '#d69a00', '#ffffff']
color does not have built-in scale generation. You would need to manually calculate steps and interpolate between instances, which adds significant boilerplate.
import Color from 'color';
// Manual interpolation required (no built-in scale helper)
const start = Color('#000000');
const end = Color('#ffffff');
const step = 0.25;
// You must manually mix and map indices
const mid = start.mix(end, step);
console.log(mid.hex());
// Output: '#404040' (Requires custom loop logic for full scales)
polished is not designed for generating data scales. It focuses on single-color adjustments relative to a theme. Trying to build a scale here requires manual iteration similar to color.
import { lighten } from 'polished';
// No scale function; must manually apply adjustments in a loop
const baseColor = '#000000';
const step1 = lighten(0.2, baseColor);
const step2 = lighten(0.4, baseColor);
console.log(step1, step2);
// Output: '#333333', '#666666' (Linear RGB steps, not perceptual)
tinycolor2 offers basic mixing but lacks advanced color space modes. Interpolation happens in RGB/HSL, often resulting in less vibrant transitions for complex gradients.
import tinycolor from 'tinycolor2';
// Basic mixing available, but no 'mode' option for LCH/Lab
const result = tinycolor.mix('#000000', '#ffff00', 50);
console.log(result.toHexString());
// Output: '#808000' (Standard RGB/HSL mix)
How you adjust a color (making it lighter, darker, or shifting hue) depends on whether you are tweaking a UI theme or performing scientific correction.
polished shines in CSS-in-JS theming. Its API is designed to read like design language (lighten, darken, transparentize).
import { lighten, transparentize } from 'polished';
const primary = '#3498db';
// Designed for theme objects
const hoverState = lighten(0.1, primary);
const disabledState = transparentize(0.5, primary);
console.log(hoverState); // '#4baaf0'
console.log(disabledState); // 'rgba(52, 152, 219, 0.5)'
chroma-js provides deep control over specific color channels (L, C, H, R, G, B) allowing for precise scientific adjustments.
import chroma from 'chroma-js';
const c = chroma('#3498db');
// Manipulate specific LCH channels directly
const brighter = c.set('l.l', '+20'); // Increase Lightness
const shifted = c.set('h.h', '+30'); // Rotate Hue
console.log(brighter.hex());
console.log(shifted.hex());
color uses an immutable chainable API. This is excellent for functional programming patterns where you don't want to mutate original variables.
import Color from 'color';
const base = Color('#3498db');
// Chain operations without mutating 'base'
const modified = base.lighten(0.1).rotate(30);
console.log(modified.hex());
// Original 'base' remains unchanged
tinycolor2 uses a mutable object pattern (or returns new instances depending on usage), which can be simpler but less safe in complex state management.
import tinycolor from 'tinycolor2';
const base = tinycolor('#3498db');
// Returns a new instance for most operations
const lighter = base.lighten(10).toHexString();
console.log(lighter);
// Output: '#4baaf0' (Approximate, based on HSL lightness)
In enterprise apps, colors come from everywhere: APIs, user inputs, CSS files. Your parser must be forgiving yet accurate.
color is arguably the most robust parser for strict format conversion. It handles obscure CSS formats (like HWB) and validates them rigorously.
import Color from 'color';
// Parses complex CSS strings including HWB and HSL
const c = Color('hwb(194, 0%, 0%)');
console.log(c.rgb().string());
// Output: 'rgb(0, 204, 255)'
console.log(c.isDark()); // Boolean check
chroma-js is also extremely flexible, often accepting arrays of numbers which is great for data coming from binary buffers or WebGL contexts.
import chroma from 'chroma-js';
// Accepts arrays [r, g, b] or strings
const c1 = chroma(255, 0, 0);
const c2 = chroma('rgb(255, 0, 0)');
console.log(c1.hex()); // '#ff0000'
console.log(c2.css()); // 'rgb(255, 0, 0)'
tinycolor2 is very forgiving with input strings, making it good for cleaning up messy user data, but it lacks support for newer CSS color spaces like OKLCH.
import tinycolor from 'tinycolor2';
// Handles messy inputs well
const c = tinycolor('rgb 255 0 0'); // Non-standard spacing
console.log(c.toHexString());
// Output: '#ff0000'
polished expects clean inputs primarily because it acts as a transformer for existing theme values rather than a raw parser for arbitrary strings.
import { rgba } from 'polished';
// Assumes valid hex or rgb string input
const result = rgba('#FF0000', 0.5);
console.log(result);
// Output: 'rgba(255, 0, 0, 0.5)'
A critical architectural decision is future-proofing. The CSS Color Module Level 4 and 5 introduce new spaces like OKLCH and OKLAB.
chroma-js: Actively maintained and regularly updated to support new color spaces. It is the safest bet for long-term projects.color: Well-maintained, focuses on stability and correctness of existing standards. Good for production backends and strict validation.polished: Maintained in sync with styled-components ecosystem. Ideal as long as you stay within standard RGB/HSL manipulations.tinycolor2: Caution advised. While still widely used, it is considered legacy by many architects. It lacks native support for modern color spaces (OKLCH, P3) and has seen slower evolution. For new greenfield projects, chroma-js is generally preferred over tinycolor2.| Feature | chroma-js | color | polished | tinycolor2 |
|---|---|---|---|---|
| Primary Use Case | Data Viz & Scales | Parsing & Conversion | CSS-in-JS Theming | Legacy / Simple Utils |
| Color Spaces | RGB, HSL, LCH, Lab, OKLCH | RGB, HSL, HWB, HSLuv | RGB, HSL (CSS focused) | RGB, HSL |
| Scale Generation | ā Advanced (Multi-stop, modes) | ā Manual | ā Manual | ā ļø Basic |
| Immutability | Mutable (mostly) | ā Immutable | Pure Functions | Mixed |
| Bundle Weight | Heavy | Medium | Light | Very Light |
| Modern CSS Support | ā High | ā ļø Medium | ā ļø Medium | ā Low |
If you are building a data visualization dashboard or a design system that needs smooth, perceptual gradients, chroma-js is the only professional choice. Its ability to work in LCH space prevents the "muddy middle" problem common in RGB interpolation.
If you are working in a CSS-in-JS environment (like Styled Components) and need to derive button hover states or disabled opacities from a theme object, polished offers the best developer experience and readability.
If you need a strict, immutable utility to normalize colors coming from an API or user input before storing them in a database, color provides the most robust validation and conversion API.
Avoid tinycolor2 for new, complex applications. While it is small and simple, its lack of modern color space support makes it a technical debt risk for projects aiming to support wide-gamut displays or future CSS standards.
Choose chroma-js when building data visualizations, heatmaps, or complex design systems that require perceptually uniform color scales (e.g., viridis, magma). It is the only option here that supports advanced color spaces like LCH and Lab, making it essential for algorithms that need to interpolate colors smoothly without shifting hue unpredictably. Avoid it for simple CSS string manipulation if bundle size is a primary concern, as it is heavier than the alternatives.
Select color when you need a strict, immutable API for parsing user input or normalizing color formats across a large codebase. It excels at validation and conversion between formats (HEX, RGB, HSL, HWB) without mutating the original instance, which reduces bugs in concurrent operations. It is less suitable if you need built-in CSS mixins or complex scale generation, as it focuses purely on the color object itself rather than styling utilities.
Use polished if your project relies heavily on CSS-in-JS libraries like styled-components or emotion and you need quick, readable helpers for theme variations. It is designed to work seamlessly with JavaScript theme objects, providing functions like adjustHue or desaturate that return ready-to-use CSS strings. It is not a standalone color science library; avoid it if you need deep color space analysis or non-CSS-related color logic.
Opt for tinycolor2 only for legacy projects or extremely constrained environments where you need basic conversion and readability checks with a minimal footprint. It is useful for simple tasks like checking if text should be black or white over a background color. Do not use it for new projects requiring modern color spaces (like LCH) or complex interpolations, as it is no longer actively evolved compared to chroma-js.
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