@ctrl/tinycolor
@ctrl/tinycolor is a TypeScript color parser, converter, and manipulation library descended from tinycolor2. `TinyColor` accepts hex, RGB, HSL, HSV, CMYK, named colors, numbers, and component objects, then formats or modifies them and builds palettes. Separate exports cover ratio inputs, seeded random colors, WCAG 2.0 contrast, readability checks, and foreground selection. It ships CommonJS and tree-shakeable module builds with declarations and no runtime dependencies.
A capable, typed all-purpose color utility for UI code, especially when permissive input and tinycolor2-compatible operations are useful. Put validation and cloning wrappers around it; choose a perceptual color library when modern color spaces or visualization-grade interpolation drive the work.
Use it if
- You need one typed API to accept several common color formats and normalize output
- You generate tints, shades, complements, or multi-color palettes in frontend tooling
- You need WCAG 2.0 contrast ratios and readable foreground selection from a candidate list
- You are migrating from tinycolor2 and want a maintained TypeScript fork with named exports
- You require strict parsing: the README calls parsing very permissive, and an invalid TinyColor behaves like black unless you check the `isValid` property
- You expect immutable transformations: `setAlpha`, `lighten`, `darken`, `mix`, `spin`, and other modification methods mutate the current object and return it for chaining
- You need modern perceptual spaces or CSS Color 4 coverage such as Lab, LCH, OKLab, OKLCH, `color()`, or `color-mix()`: the documented formats center on RGB, HSL, HSV, CMYK, hex, and named colors
- You need contrast decisions for translucent colors without a known background: use `onBackground` first because transparency must be composited before a meaningful displayed contrast ratio exists
- You only need one conversion in performance-sensitive code: this class, parser, name table, manipulation, palette, random, and readability surface is more machinery than a focused conversion function
Setup reality
Install with npm install @ctrl/tinycolor. Version 4.2.0 has no runtime or peer dependencies, no native build, credentials, global CSS, config, or initialization. It publishes a CommonJS main entry, a tree-shakeable module entry, `sideEffects: false`, and TypeScript declarations; package metadata requires Node 14 or later. Import named exports such as `TinyColor`, `readability`, `isReadable`, `mostReadable`, `fromRatio`, and `random`. There is no default export, which is the first migration surprise for tinycolor2 users, and helpers such as readability and fromRatio are standalone exports rather than static methods on the class. Parsing is intentionally forgiving: optional punctuation and several numeric conventions make user input convenient, but malformed input does not throw. Always inspect the `isValid` property before storing or rendering a user-supplied color because invalid instances continue through other methods as black. `isValid` and `format` are properties, not functions. Modification methods mutate the instance and return it, so clone before deriving variants from a shared base. Saturation and lightness returned by `toHsl()` are fractions even though formatted HSL strings use percentages. Alpha changes output shape, for example `toRgbString()` switches to rgba syntax. For accessible translucent colors, composite with `onBackground()` before checking contrast. The readability helpers implement the documented WCAG 2.0 ratio model and require you to choose AA or AAA plus small or large text; they do not decide typography eligibility for you. Seed `random()` when snapshots or server-rendered output must repeat.
Patterns
Parse a color without accepting invalid blackparse-and-validate
import { TinyColor } from '@ctrl/tinycolor';
const color = new TinyColor(userInput);
if (!color.isValid) throw new Error('Invalid color');
console.log(color.toHexString());Invalid instances do not throw and behave like black, so check the isValid property before using them.
Normalize supported input to six-digit hexnormalize-hex
import { TinyColor } from '@ctrl/tinycolor';
function normalizeHex(input: string) {
const color = new TinyColor(input);
return color.isValid ? color.toHexString() : null;
}
console.log(normalizeHex('rgb(54, 153, 204)')); // #3699cctoHexString omits alpha; use toHex8String when transparency must survive.
Parse and format an alpha colorpreserve-alpha
import { TinyColor } from '@ctrl/tinycolor';
const color = new TinyColor('rgba(255, 0, 0, 0.5)');
console.log(color.toRgbString());
console.log(color.toHex8String()); // #ff000080Eight-digit hex places alpha last, and channel rounding can make 0.5 appear as 0x80.
Convert a color to HSL componentsconvert-color-space
import { TinyColor } from '@ctrl/tinycolor';
const hsl = new TinyColor('#ff8000').toHsl();
console.log(hsl); // h, s, l, a
console.log(new TinyColor('#ff8000').toHslString());toHsl returns saturation and lightness as fractions; toHslString displays percentages.
Create a lighter variant without changing the basederive-without-mutation
import { TinyColor } from '@ctrl/tinycolor';
const base = new TinyColor('#2563eb');
const lighter = base.clone().lighten(15);
console.log(base.toHexString(), lighter.toHexString());lighten mutates its instance. clone first when the original color is still needed.
Mix two colorsmix-colors
import { TinyColor } from '@ctrl/tinycolor';
const mixed = new TinyColor('#ff00ff')
.mix('#00ff00', 50)
.toHexString();
console.log(mixed); // #808080mix mutates the first TinyColor; the amount is a percentage from 0 to 100.
Generate a tetradic palettegenerate-palette
import { TinyColor } from '@ctrl/tinycolor';
const palette = new TinyColor('#ef4444')
.tetrad()
.map((color) => color.toHexString());
console.log(palette);Combination methods return TinyColor objects, not strings; format each result explicitly.
Calculate a WCAG contrast ratiocalculate-contrast
import { readability } from '@ctrl/tinycolor';
const ratio = readability('#111827', '#ffffff');
console.log(ratio);The result is a ratio. Passing a threshold depends on text size and the conformance level you target.
Check AA contrast for small textcheck-aa-readability
import { isReadable } from '@ctrl/tinycolor';
const passes = isReadable('#111827', '#ffffff', {
level: 'AA',
size: 'small',
});
console.log(passes);Choose small or large based on the rendered typography; the library cannot infer font size and weight.
Choose black or white foreground textchoose-text-color
import { mostReadable } from '@ctrl/tinycolor';
const foreground = mostReadable('#7c3aed', ['#000', '#fff'], {
includeFallbackColors: true,
level: 'AA',
size: 'small',
});
console.log(foreground.toHexString());The return value is a TinyColor. includeFallbackColors permits black or white even when they were not candidates.
Composite transparency before checking contrastcomposite-alpha
import { TinyColor, readability } from '@ctrl/tinycolor';
const background = '#ffffff';
const displayed = new TinyColor('rgba(37, 99, 235, 0.5)')
.onBackground(background);
console.log(readability(displayed, background));A translucent foreground has no fixed displayed color until its background is known.
Generate repeatable random colorsgenerate-seeded-color
import { random } from '@ctrl/tinycolor';
const color = random({
seed: 42,
hue: 'blue',
luminosity: 'light',
});
console.log(color.toHexString());Supply an integer seed for deterministic tests or server rendering; an unseeded call is intentionally variable.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| colord | npm | You want a smaller immutable core and can add only the color-space plugins your app needs |
| color | npm | You prefer an immutable chainable API with CSS color parsing and conversion |
| chroma-js | npm | You need scales, interpolation, statistics, or perceptual color spaces for visualization |
| tinycolor2 | npm | You must preserve the original tinycolor2 default export and legacy API in an existing codebase |