Qix-/color: Immutable Color Conversion and Manipulation for JavaScript
GitHub describes it as :rainbow: Javascript color conversion and manipulation library. The repository metadata lists JavaScript as its primary language. The metadata lists the MIT license. This article stays within the project description and details documented in the GitHub repository README.
At a glance
- What is it?
- A small ESM library that parses CSS color strings, converts between color spaces, and returns new color objects from every manipulation. It suits build-time and server-side color math, not live pixel editing.
- Who is it for?
- Adopt Qix-/color when you need deterministic color math in a Node 18+ or bundler-based project: contrast checks, palette generation, theme derivation, or converting stored colors between spaces. Do not adopt it as a browser color picker or image sampler; it has no UI and no pixel access.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Qix-/color solves: color math without mutation
Most color handling in JavaScript ends up as string concatenation. You store "#7743CE", then need a lighter variant for a hover state, a contrast ratio against white for an accessibility check, and an ANSI escape code for terminal output. Doing that by hand means writing parsers for hex, rgb(), hsl(), and the four-value hsl() form that carries alpha. Qix-/color packages that work behind one constructor.
The audience is narrow and specific. This is for developers who compute colors in code: theme generators, design-token pipelines, chart libraries, CLI tools that print colored output, and server-side code that validates contrast. The README's opening example shows the whole shape of the API, chaining alpha, lighten and a conversion before printing a string. Every step returns a new color object, so a base color can be reused across a chain without being modified. That immutability is the design decision the rest of the API is built around, and it is why the library is safe to share a color constant across modules.
How the immutable conversion pipeline works
A color object carries a model name and a value array. The README's constructor examples spell this out: Color('rgb(255, 255, 255)') produces { model: 'rgb', color: [ 255, 255, 255 ], valpha: 1 }, while Color('hsl(194, 53%, 79%, 0.5)') produces an hsl model with valpha 0.5. The parsed model is preserved, not normalized to RGB, which is why color.object() and color.array() are documented as reflecting the color's current model.
String parsing is delegated to color-string, and numeric conversion to color-convert; both are listed as runtime dependencies in package.json. Getters such as hsl() and cmyk() return a new color in the target space, and the accessors that follow (array(), object(), string()) read from that space. Manipulation methods follow the same pattern. The README documents lighten and darken as HSL lightness operations, saturate and desaturate as HSL saturation operations, and grayscale as a desaturation to a gray value. Two of the documented examples are worth noting because they show clamping: lighten(0.5) on hsl(100, 50%, 0) stays at lightness 0, and darken(0.5) on the same color also stays at 0. The library does not let a manipulation push a channel outside its range.
Accessibility helpers sit on top of the same model. luminosity() returns the WCAG relative luminance, contrast() returns the WCAG ratio from 1 to 21, and isLight() and isDark() are shortcuts for choosing text color. These are the parts of the API with an external specification behind them, which makes them the easiest to trust and the easiest to verify.
Installing Qix-/color and running a first contrast check
The README gives a single install command. The package is ESM-only: package.json sets "type": "module" and exports "./index.js", and engines requires Node 18 or newer.
npm install colorImport the default export and construct a color from any CSS string the parser accepts. The README's example uses an uppercase hex string:
const color = Color('#7743CE').alpha(0.5).lighten(0.5);
console.log(color.hsl().string()); // 'hsla(262, 59%, 81%, 0.5)'The README states that this prints 'hsla(262, 59%, 81%, 0.5)'. Note the ordering: alpha is set first, then lightness is raised, and the final hsl() conversion carries both changes into the printed string.
A realistic first use is checking whether white text is readable on a brand color. The README documents contrast() as returning a WCAG ratio between 1 and 21, and isLight() as a boolean:
color.contrast(Color("blue")) // 12
color.isLight() // true
color.isDark() // falseIf you need the value as data rather than a string, rgbNumber() returns the packed integer (the README shows 16777215 for white) and hex() returns the hex string. One documented trap: hex() does not include alpha, so an RGBA representation requires hexa().
Where the immutability and the API surface get in the way
Every manipulation allocates. Chaining five operations produces five color objects, and each conversion between spaces recomputes the underlying values. For a theme generator or a one-off palette build this is irrelevant. For a render loop that derives hundreds of colors per frame, the allocation pattern is the wrong fit, and precomputing the palette once is the only sensible approach.
The API is also deliberately low-level. There is no palette generator, no color wheel, no image sampling, no color-blindness simulation, and no picker component. The related searches around palettes, pickers and color-blind tests describe tools that do not exist here. If you need to read a pixel from a canvas or an uploaded image, this library will not help you; it operates on color values you already have.
Two more constraints come from the packaging. The package is ESM-only, so a CommonJS codebase needs a dynamic import or a bundler that handles ESM. The published files array contains only LICENSE, index.js and index.d.ts, and index.js depends on color-convert and color-string, so the install pulls those in transitively. The README also does not document rollback or version pinning behavior, and it says nothing about how breaking changes are communicated between major versions; the release history shows a jump from 5.0.0 to 5.0.2 to 5.0.3, but the README itself is silent on migration.
Qix-/color versus chroma.js and versus hand-rolled math
chroma.js is the obvious alternative, and the difference is architectural rather than cosmetic. chroma.js is a larger library that also covers scale generation, color interpolation, palette building and color-blindness simulation, and it ships in formats that include CommonJS. Qix-/color stays close to conversion and per-channel manipulation, delegates parsing and numeric conversion to two focused dependencies, and keeps the object model immutable. If your requirement is a sequential scale between two brand colors, chroma.js has that built in and Qix-/color does not.
The other alternative is writing the math yourself. Hex-to-RGB is trivial; WCAG relative luminance is a short formula; the contrast ratio is a division. What you give up is the parsing breadth. color-string handles the CSS forms the README lists, including the four-argument hsl() with alpha, and the model-preserving object shape means you can round-trip a value without guessing which space it started in. That is the part worth not rewriting.
Maintenance, licence and upgrade cost
The repository is not archived. The most recent push recorded for the default branch is 2025-11-14, which is the same date as the 5.0.3 release, and 5.0.2 preceded it on 2025-09-13. The README does not describe a support window or a deprecation policy, so treat the release cadence as the only signal available.
The licence is MIT, declared in package.json and shipped as a LICENSE file in the published package. MIT is permissive; it does not impose copyleft obligations on your application. This is a description of the licence text, not legal advice, and if you redistribute the library in a product with unusual licensing requirements, read the LICENSE file itself.
The upgrade cost is bounded by the API's shape. Manipulation methods return new objects and getters return new objects, so a breaking change in a major version tends to surface as a changed return type or a renamed accessor rather than as silent data corruption. The test setup is visible in package.json: the test script runs xo, tsd and mocha, and the repository contains index.test-d.ts alongside a test directory, so type-level behavior is checked as part of the suite. That is a reasonable signal for a library whose main export is a TypeScript-typed default function.
Editorial conclusion
Adopt Qix-/color when you need deterministic color math in a Node 18+ or bundler-based project: contrast checks, palette generation, theme derivation, or converting stored colors between spaces. Do not adopt it as a browser color picker or image sampler; it has no UI and no pixel access. Before committing, verify that your build consumes the ESM-only entry point ("type": "module", exports "./index.js") and that your toolchain resolves the shipped index.d.ts, since the package lists only LICENSE, index.js and index.d.ts in its files array.
Frequently asked questions
How do I install Qix-/color?
The README gives a single command: npm install color. The package is ESM-only, and package.json declares Node 18 or newer in its engines field.
Does Qix-/color modify the original color object when I call lighten or saturate?
No. The README describes the library as immutable color conversion and manipulation, and its examples chain calls such as alpha(0.5).lighten(0.5) from a single constructed value, with each call returning a new color object.
Does Qix-/color include alpha in the hex output?
No. The README states explicitly that .hex() does not return alpha values, and that .hexa() should be used for an RGBA representation.
Can Qix-/color generate a color palette or a color wheel?
The README documents conversion, per-channel manipulation, CSS string output and WCAG luminosity and contrast helpers. It does not document palette generation, a color wheel, or image sampling.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/qix-color)