# tinycolor: a small TypeScript library for parsing and manipulating colors

> TinyColor is a fork of tinycolor2 rewritten in TypeScript, with a class-based API, tree shakeable ES module output and permissive string parsing that accepts hex, rgb, hsl, hsv, cmyk, named and numeric input. It is MIT licensed and published as @ctrl/tinycolor.

**scttcper/tinycolor** — 🎨 Color manipulation and conversion

- Repository: https://github.com/scttcper/tinycolor
- Website: https://tinycolor.vercel.app
- Stars: 614 · Forks: 43
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/scttcper-tinycolor

## A fork with a class API and tree shakeable output

TinyColor is a small library for color manipulation and conversion, and it is a fork of tinycolor2 by Brian Grinstead.

The fork exists for two reasons that are visible in the package metadata and the README. The code was reformatted into TypeScript and es2015 and requires node 8 or newer, and the package declares sideEffects false with a tree shakeable module export. The other reason is a deliberate API change: tinycolor is now exported as a class called TinyColor, and the default export was removed, so the import is named.

That last point is the migration cost for anyone coming from tinycolor2, and it is not the only one.

Several functions moved out of the class and are no longer reachable as TinyColor.something. readability and fromRatio moved out. random moved out and was renamed to legacyRandom, which tells you the name now belongs to a different implementation, an implementation of randomColor by David Merfield that returns a TinyColor object. And toFilter moved out and was renamed to toMsFilter, the ms presumably meaning the CSS filter function syntax.

Two more methods changed signature. mix and equals now take the current TinyColor object as the first parameter rather than accepting loose arguments.

Two more were added. The fork picked up polyad colors from tinycolor pull request 126, and it added tint() and shade() from tinycolor pull request 159.

Two behavioural changes are worth calling out because they are not obvious from a type signature. Color wheel values of 360 are allowed to over-spin or under-spin and still return a valid color, from tinycolor pull request 108. And isValid and format are now properties rather than functions, so they are read without parentheses.

The primary language is TypeScript, the licence is MIT, the default branch is master, and the last push was on 2026-09-09 with v4.2.1 tagged the same day.

## The parser accepts more formats than you would design on purpose

The string parsing is very permissive, and the README says why: it is meant to make typing a color as input as easy as possible. All commas, percentages and parentheses are optional, and most inputs allow either 0-1, 0%-100% or 0-n, where n is 100, 255 or 360 depending on the value.

The input list in the README covers hex in three, four, six and eight digit forms, with or without the leading hash.

```ts
new TinyColor('#000');
new TinyColor('000');
new TinyColor('#369C');
new TinyColor('369C');
new TinyColor('#f0f0f6');
new TinyColor('f0f0f6');
new TinyColor('#f0f0f688');
new TinyColor('f0f0f688');
```

Then rgb and rgba, with spaces, commas or parentheses, or as a plain object.

```ts
new TinyColor('rgb (255, 0, 0)');
new TinyColor('rgb 255 0 0');
new TinyColor('rgba (255, 0, 0, .5)');
new TinyColor({ r: 255, g: 0, b: 0 });
```

Then hsl and hsla, then hsv and hsva, then cmyk, then named colors, then a plain number.

```ts
new TinyColor('hsl(0, 100%, 50%)');
new TinyColor('hsla(0, 100%, 50%, .5)');
new TinyColor('hsl 0 1.0 0.5');
new TinyColor({ h: 0, s: 1, l: 0.5 });
```

```ts
new TinyColor('cmyk(0, 25, 20, 0)');
new TinyColor({c: 0, m: 25, y: 25, k: 0});
```

```ts
new TinyColor(0x0);
new TinyColor(0xaabbcc);
```

Two rules narrow that permissiveness, and they are stated as rules rather than preferences. HSL and HSV both require either 0%-100% or 0-1 for the saturation, lightness or value properties, while the hue can be anywhere between 0%-100% or 0-360. And RGB input requires either 0-255 or 0%-100%, with the exception that fromRatio lets RGB and hue input accept 0-1.

The practical effect is that fromRatio is the function to reach for when your numbers are already in the 0-1 range, since the main constructor will not accept that shape for RGB.

```ts
import { fromRatio } from '@ctrl/tinycolor';
fromRatio({ r: 1, g: 0, b: 0 });
fromRatio({ r: 0.5, g: 0.5, b: 0.5 });
```

## An invalid color is not an error, it is black

This is the single most important behavioural detail in the library, and it is easy to miss.

The isValid property is a boolean indicating whether the color was successfully parsed, and the README notes that if the color is not valid then it will act like black when being used with other methods.

```ts
const color1 = new TinyColor('red');
color1.isValid; // true
color1.toHexString(); // "#ff0000"

const color2 = new TinyColor('not a color');
color2.isValid; // false
color2.toString(); // "#000000"
```

There is no exception. A parse failure produces an object whose isValid is false and whose every other method returns black.

That design has a defensible logic behind it. A library that throws on a malformed colour is one you have to wrap every call in a try block, and a library that returns black is one where the failure is visible through isValid and silent everywhere else. For a UI that reads a colour from a user input field and falls back to a default, that is convenient.

It is also a trap. If you never check isValid, a typo in a hex string produces a black element rather than an error, and you will spend time looking at your stylesheet rather than at your parser. The check is one property read, and the README makes it visible on the properties page, so it is discoverable.

Two related properties carry the same kind of information. originalInput returns the original input passed into the constructor, so you can see what the user actually typed. format returns the format used to create the instance, so you can branch on whether a colour arrived as a name or as rgb.

The other direction, and the one people reach for most often, is the alpha handling. getAlpha returns the alpha value from 0-1, setAlpha sets it in the accepted range, and the toRgbString output reflects it.

```ts
const color = new TinyColor('red');
color.getAlpha(); // 1
color.setAlpha(0.5);
color.getAlpha(); // .5
color.toRgbString(); // "rgba(255, 0, 0, .5)"
```

## Brightness and luminance are two different numbers for accessibility

The library ships two perceptual brightness measures, and they are not interchangeable, which is why both exist.

getBrightness returns the perceived brightness of a color from 0-255, as defined by the Web Content Accessibility Guidelines version 1.0. isLight and isDark are thin wrappers that turn that into a boolean.

```ts
const color1 = new TinyColor('#fff');
color1.getBrightness(); // 255

const color2 = new TinyColor('#000');
color2.getBrightness(); // 0
```

getLuminance returns the perceived luminance of a color from 0-1, as defined by the Web Content Accessibility Guidelines version 2.0. The WCAG 2.0 definition is the relative luminance used in the contrast ratio formula, which is a different computation from the version 1.0 brightness measure and produces different values for the same colour.

```ts
const color1 = new TinyColor('#fff');
color1.getLuminance(); // 1

const color2 = new TinyColor('#000');
color2.getLuminance(); // 0
```

That distinction matters when you are checking contrast. The two figures are defined against different revisions of the accessibility guidelines, and picking the wrong one gives you a plausible-looking number that does not match the formula your design system or your compliance audit is using.

The isLight and isDark pair is the quickest of the three.

```ts
const color1 = new TinyColor('#fff');
color1.isLight(); // true

const color2 = new TinyColor('#000');
color2.isDark(); // true
```

And there is a composite method for the case where you have a translucent foreground over an opaque background. onBackground computes how the color would appear on a background. When the color is fully transparent the result is the background color, when it is fully opaque the result is the color itself, and in between you get a computed result.

That is the method to reach for when a design system has translucent tokens and needs the effective colour for a contrast check, rather than the declared colour.

## A pnpm monorepo with vitest, rollup and a size-conscious build

The repository layout shows the development setup in more detail than the README does.

The package manifest is for @ctrl/tinycolor, published publicly with npm provenance enabled. The entry points are dist/public_api.js for CommonJS, dist/module/public_api.js for the module build and dist/public_api.d.ts for the typings, and only the dist directory is included in the published files.

The build script chains three steps.

```bash
"build": "del-cli dist && tsc -p tsconfig.build.json && tsc -p tsconfig.module.json && ts-node build"
```

Two separate TypeScript configurations produce the two module trees, and then a build script driven by ts-node does the final packaging. The presence of tsconfig.build.json and tsconfig.module.json at the root matches that.

Testing is vitest, with a separate benchmark configuration in vitest.bench.config.mts and a bench script that runs vitest bench against it. There is a benchmarks/ directory at the root, and a bundlephobia badge in the README, which is the standard way of tracking package size. The project takes size seriously enough to have that badge and a build that strips and minifies through rollup with the terser plugin.

The demo is a separate workspace package under demo/ with its own package.json and tsconfig, and there are demo:build and demo:watch scripts. The docs are generated with typedoc into the demo directory, pointing at src/public_api.ts as the entry point, so the API reference is produced from the same source as the library rather than written by hand.

The workspace is managed with pnpm, with pnpm-workspace.yaml and pnpm-lock.yaml at the root, and there is a .nvmrc pinning the Node version for development. The eslint config comes from @ctrl/eslint-config and there is a prettier config, so formatting and linting are enforced by the shared tooling rather than ad hoc.

The release configuration names master as the release branch, and the recent tags show the cadence: v4.2.1 from 2026-09-09, v4.2.0 from 2025-09-16 and v4.1.0 from 2024-04-13.

## Where tinycolor is the wrong tool

The first wrong-tool case is a design system that needs perceptual colour difference. The library gives you brightness, luminance, alpha and format conversion, and that is where its perceptual support stops. There is no delta-E calculation, no CIE Lab or LCH space, no colour difference metric and no palette generation. If you are building a token pipeline that has to decide whether two swatches are close enough to merge, or generate accessible ramps, this is not the library and the gap is not small.

The second case is a colour space library. TinyColor handles hex, rgb, hsl, hsv and cmyk. It does not handle oklch, lab, lch, hwb or wide gamut spaces, and the topic tags name only colour-manipulation and tinycolor. Modern CSS colour work and any design system built around perceptual lightness needs a different dependency.

The third case is a codebase already on tinycolor2 that has not decided to accept the breaking changes. The migration is not a version bump: the import changes from a default export to a named class export, readability and fromRatio leave the class, random becomes legacyRandom, toFilter becomes toMsFilter, mix and equals change their first parameter, and isValid and format change from calls to property reads. Every one of those is a compile error rather than a subtle behaviour change, which is the good case, but it is still a migration across every call site.

The fourth case is a project that wants colour management. Nothing here deals with device profiles, colour spaces outside the CSS set, or rendering intent. TinyColor treats colour as a tuple of numbers, which is the right level for UI work and the wrong level for print or anything that has to survive a conversion to another device.

The invalid-colour-becomes-black behaviour is worth restating as a limitation too, because it is the trap most likely to cost someone an afternoon. Any code path that reads a colour from an untrusted source should check isValid explicitly, and the README does not document a strict mode that turns the silent fallback into a thrown error.

On licence, the project is MIT with the LICENSE file at the root, and the npm package declares MIT as well with provenance on. For a dependency that ends up in every component of a design system, that is the uncomplicated case. The remaining cost of adoption is the API churn rather than the terms: the fork has already renamed and relocated several public functions once, and the beta-era caveats do not apply here, but the pattern of the library is a small, actively reshaped surface rather than a frozen one.

## Conclusion

TinyColor suits a TypeScript codebase that needs to read a color from a user, a config file or a database and turn it into whatever format the next layer expects, because the parsing is permissive and the conversion methods are exhaustive. It does not suit a project that needs perceptual colour difference metrics, since the library offers brightness and luminance but no distance or delta-E calculation, and it does not suit a codebase already on tinycolor2 that has not decided to accept the API changes. Before adopting, check the renamed and relocated functions, since readability, fromRatio, random and toFilter are no longer on the class, and note that isValid and format are properties rather than methods in this fork.

## FAQ

### What is TinyColor?

TinyColor is a small library for color manipulation and conversion, and a fork of tinycolor2 by Brian Grinstead. It was reformatted into TypeScript and es2015, exports a class called TinyColor rather than a default export, and ships a tree shakeable module build with no package sideEffects.

### How do I install and use TinyColor?

Run npm install @ctrl/tinycolor, then import the named export with import { TinyColor } from '@ctrl/tinycolor' and construct a colour, for example new TinyColor('red').toHexString() which returns '#ff0000'. The package is published as @ctrl/tinycolor.

### What happens if TinyColor cannot parse a color?

No exception is thrown. The isValid property is set to false and the instance behaves like black when used with other methods, so a bad input such as 'not a color' returns '#000000' from toString. Check isValid explicitly if you need to know the parse failed.

### What is the difference between getBrightness and getLuminance in TinyColor?

getBrightness returns perceived brightness from 0-255 as defined by the Web Content Accessibility Guidelines version 1.0, while getLuminance returns relative luminance from 0-1 as defined by WCAG 2.0. The two follow different definitions, so pick the one your contrast checks are based on.

### What changed between tinycolor2 and this TinyColor fork?

The default export was removed in favour of the named TinyColor class export, readability and fromRatio moved out of the class, random moved out and was renamed to legacyRandom, toFilter moved out and was renamed to toMsFilter, mix and equals take the TinyColor object as their first parameter, isValid and format became properties instead of functions, and tint() and shade() plus polyad color support were added.

### What license is TinyColor released under?

TinyColor is released under the MIT license, with the LICENSE file at the root of the repository and MIT declared in the package metadata. The current release is v4.2.1, tagged on 2026-09-09.

## Sources

- [License: MIT](https://github.com/scttcper/tinycolor/blob/master/LICENSE)
- [Project website](https://tinycolor.vercel.app)
- [README](https://github.com/scttcper/tinycolor/blob/master/README.md)
- [Releases](https://github.com/scttcper/tinycolor/releases)
- [scttcper/tinycolor on GitHub](https://github.com/scttcper/tinycolor)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/scttcper-tinycolor
