# colorthief

> Extract dominant colors and palettes from images — TypeScript, OKLCH, semantic swatches, live video extraction.

Latest version **3.5.0** (published 2026-08-02) · MIT license · 0 weekly downloads

## Install

```sh
npm install colorthief
pnpm add colorthief
yarn add colorthief
bun add colorthief
```

Provides the command `colorthief`.

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score; popular repo.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 3.5.0 |
| Published | 2026-08-02 |
| First published | 2017-11-27 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 1.6 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 13650 |
| Author | Lokesh Dhakar |
| Maintainers | lokesh.dhakar |
| Keywords | color, palette, sampling, image, picture, photo, canvas, oklch, swatch |

## Links

- npm: https://www.npmjs.com/package/colorthief
- Repository: https://github.com/lokesh/color-thief
- Homepage: http://lokeshdhakar.com/projects/color-thief/
- Issues: https://github.com/lokesh/color-thief/issues
- npm.io page: https://npm.io/package/colorthief

## Alternatives

- [exif-parser](https://npm.io/package/exif-parser.md) — 3.8M weekly downloads
- [vite-plugin-compression](https://npm.io/package/vite-plugin-compression.md) — 569.5K weekly downloads
- [pica](https://npm.io/package/pica.md) — 442.4K weekly downloads
- [@reportportal/client-javascript](https://npm.io/package/@reportportal/client-javascript.md) — 408.8K weekly downloads
- [@tldraw/state](https://npm.io/package/@tldraw/state.md) — 316.0K weekly downloads

## Recent versions

- 3.5.0 (latest) — 2026-08-02
- 3.4.1 — 2026-08-02
- 3.4.0 — 2026-07-01
- 3.3.1 — 2026-03-06
- 3.3.0 — 2026-03-04
- 3.2.0 — 2026-03-04
- 3.1.0 — 2026-03-04
- 3.0.0 — 2026-03-02
- 2.7.0 — 2026-02-28
- 2.6.0 — 2024-10-09
- 2.4.0 — 2023-02-28
- 2.3.2 — 2020-07-06
- 2.3.1 — 2020-07-06
- 2.3.0 — 2019-08-04
- 2.2.1 — 2019-07-22
- … 3 more at https://npm.io/package/colorthief/versions

## README

# Color Thief

> Extract dominant colors and palettes from images in the browser and Node.js.

[![npm version](https://img.shields.io/npm/v/colorthief)](https://www.npmjs.com/package/colorthief)
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/colorthief)](https://bundlephobia.com/package/colorthief)
[![types](https://img.shields.io/npm/types/colorthief)](https://www.npmjs.com/package/colorthief)

## Install

```bash
npm install colorthief
```

Or load directly from a CDN:

```html
<script src="https://unpkg.com/colorthief@3/dist/umd/color-thief.global.js"></script>
```

## Quick Start

```js
import { getColorSync, getPaletteSync, getSwatches } from 'colorthief';

// Dominant color
const color = getColorSync(img);
color.hex();      // '#e84393'
color.css();      // 'rgb(232, 67, 147)'
color.isDark;     // false
color.textColor;  // '#000000'

// Palette
const palette = getPaletteSync(img, { colorCount: 6 });
palette.forEach(c => console.log(c.hex()));

// Semantic swatches (Vibrant, Muted, DarkVibrant, etc.)
const swatches = await getSwatches(img);
swatches.Vibrant?.color.hex();
```

## Features

- **TypeScript** — full type definitions included
- **Browser + Node.js** — same API, both platforms
- **Sync & async** — synchronous browser API, async for Node.js
- **Live extraction** — `observe()` watches video, canvas, or img elements and emits palette updates reactively
- **Worker friendly** — runs inside your own Web Worker via `ImageBitmap` or `OffscreenCanvas`
- **Progressive extraction** — 3-pass refinement for instant rough results
- **OKLCH quantization** — perceptually uniform palettes via `colorSpace: 'oklch'`
- **Semantic swatches** — Vibrant, Muted, DarkVibrant, DarkMuted, LightVibrant, LightMuted
- **Rich Color objects** — `.hex()`, `.rgb()`, `.hsl()`, `.oklch()`, `.css()`, contrast ratios, text color recommendations
- **WCAG contrast** — `color.contrast.white`, `color.contrast.black`, `color.contrast.foreground`
- **AbortSignal** — cancel in-flight extractions
- **CLI** — `colorthief photo.jpg` with JSON, CSS, and ANSI output
- **Zero runtime dependencies**

## API at a Glance

| Function | Description |
|---|---|
| `getColorSync(source, options?)` | Dominant color (sync, browser only) |
| `getPaletteSync(source, options?)` | Color palette (sync, browser only) |
| `getSwatchesSync(source, options?)` | Semantic swatches (sync, browser only) |
| `getColor(source, options?)` | Dominant color (async, browser + Node.js) |
| `getPalette(source, options?)` | Color palette (async, browser + Node.js) |
| `getSwatches(source, options?)` | Semantic swatches (async, browser + Node.js) |
| `getPaletteProgressive(source, options?)` | 3-pass progressive palette (async generator) |
| `observe(source, options)` | Watch a source and emit palette updates (browser only) |
| `createColor(r, g, b, population)` | Build a Color object from RGB values |

### Options

| Option | Default | Description |
|---|---|---|
| `colorCount` | `10` | Number of palette colors (2–20) |
| `quality` | `10` | Sampling rate (1 = every pixel, 10 = every 10th) |
| `colorSpace` | `'oklch'` | Quantization space: `'rgb'` or `'oklch'` |
| `region` | — | Sample a sub-rectangle: `{ x, y, width, height }` as 0–1 fractions |
| `gamut` | `'srgb'` | Output gamut: `'srgb'`, `'display-p3'`, or `'auto'` (browser only) |
| `worker` | `false` | **Deprecated** — ignored as of v3, removed in v4. See [Web Workers](#web-workers) |
| `signal` | — | `AbortSignal` to cancel extraction |
| `ignoreWhite` | `true` | Skip white pixels |

### Color Object

| Property / Method | Returns |
|---|---|
| `.rgb()` | `{ r, g, b }` |
| `.hex()` | `'#ff8000'` |
| `.hsl()` | `{ h, s, l }` |
| `.oklch()` | `{ l, c, h }` |
| `.css(format?)` | `'rgb(255, 128, 0)'`, `'hsl(…)'`, or `'oklch(…)'` — a P3 color's default `.css()` is `color(display-p3 …)` |
| `.array()` | `[r, g, b]` |
| `.gamut` | `'srgb'` or `'display-p3'` |
| `.toString()` | Hex string (works in template literals) |
| `.textColor` | `'#ffffff'` or `'#000000'` |
| `.isDark` / `.isLight` | Boolean |
| `.contrast` | `{ white, black, foreground }` — WCAG ratios |
| `.population` | Raw pixel count |
| `.proportion` | 0–1 share of total |

## Region extraction

Pass a `region` to sample only part of the image. Coordinates are fractions of
the image size (0–1) measured from the top-left, so a region is
resolution-independent — the same values work for a thumbnail and the full-size
original.

```js
// Colors from the bottom third — e.g. for a gradient that overlaps the
// bottom of the image
const palette = await getPalette(img, {
    region: { x: 0, y: 0.66, width: 1, height: 0.34 },
});

// Center crop
const color = await getColor(img, {
    region: { x: 0.25, y: 0.25, width: 0.5, height: 0.5 },
});
```

Works everywhere an option object does: `getColor`, `getPalette`, `getSwatches`,
`getPaletteProgressive`, the `*Sync` functions, `observe()`, and the CLI — in
both browser and Node. A region that runs past the right or bottom edge is
clamped to the image; out-of-range or zero-sized values throw.

## Browser

```js
import { getColorSync, getPaletteSync } from 'colorthief';

const img = document.querySelector('img');
const color = getColorSync(img);
console.log(color.hex());

const palette = getPaletteSync(img, { colorCount: 5 });
```

Accepts `HTMLImageElement`, `HTMLCanvasElement`, `HTMLVideoElement`, `ImageData`, `ImageBitmap`, and `OffscreenCanvas`.

### Wide-gamut (Display P3)

By default colors are read and reported in sRGB. For P3-tagged / wide-gamut images, pass `gamut` to preserve the extra saturation:

```js
// Force P3: read the image through a P3 canvas, report P3 colors
const palette = await getPalette(img, { gamut: 'display-p3' });
palette[0].css();            // 'color(display-p3 0.92 0.2 0.14)'
palette[0].gamut;            // 'display-p3'

// Auto: report P3 only when the image actually uses out-of-sRGB colors,
// otherwise behave exactly like sRGB
const auto = await getPalette(img, { gamut: 'auto' });
```

`.rgb()`, `.array()`, and `.hex()` always return **sRGB** (gamut-mapped when the color is P3), so existing `rgb(...)` strings keep working. The wide-gamut values live in `.css()` and `.oklch()`; use `.rgb('display-p3')` for the raw P3 components. Falls back to sRGB where P3 canvas support is unavailable. Node output is sRGB for now.

### Live extraction with observe()

```js
import { observe } from 'colorthief';

// Watch a video and update ambient lighting as it plays
const controller = observe(videoElement, {
    throttle: 200,    // ms between updates
    colorCount: 5,
    onChange(palette) {
        updateAmbientBackground(palette);
    },
});

// Stop when done
controller.stop();
```

Works with `<video>`, `<canvas>`, and `<img>` elements. For images, it uses a MutationObserver to detect `src` changes. For video and canvas, it polls using requestAnimationFrame with throttle.

### Web Workers

To keep extraction off the main thread, run Color Thief *inside* your own worker and hand it an `ImageBitmap`. Bitmaps are transferable, so the pixels move without being copied, and decoding, sampling, and quantization all happen off-thread.

```js
// main.js
const bitmap = await createImageBitmap(await (await fetch(url)).blob());
const worker = new Worker('./palette-worker.js', { type: 'module' });
worker.postMessage({ bitmap }, [bitmap]); // transferred, not cloned
worker.onmessage = (e) => render(e.data.palette);
```

```js
// palette-worker.js
import { getPalette } from 'colorthief';

self.onmessage = async ({ data }) => {
    const palette = await getPalette(data.bitmap, { colorCount: 5 });
    // Color objects don't survive structured clone — send plain data
    self.postMessage({ palette: palette.map((c) => c.hex()) });
};
```

> **Deprecated:** the `worker: true` option is ignored as of v3 and will be removed in v4, along with the `isWorkerSupported`, `extractInWorker`, and `terminateWorker` exports from `colorthief/internals`. It only moved quantization off-thread while leaving decoding, pixel sampling, and the structured clone of the pixel array on the main thread — and serializing that array cost several times more than the quantization it saved. It also quantized in RGB while the default path uses OKLCH, so `worker: true` returned different colors. Calls that pass it now go through the normal pipeline and log a one-time warning.

## Node.js

```js
import { getColor, getPalette } from 'colorthief';

const color = await getColor('/path/to/image.jpg');
console.log(color.hex());

const palette = await getPalette(Buffer.from(data), { colorCount: 5 });
```

Accepts file paths and Buffers. Uses [sharp](https://sharp.pixelplumbing.com/) for image decoding.

## CLI

### Quick start

```bash
npx colorthief-cli photo.jpg
```

The `colorthief-cli` package bundles everything needed (including sharp for image
decoding), so it works immediately with no extra setup.

### Commands

```bash
# Dominant color
colorthief-cli photo.jpg

# Color palette
colorthief-cli palette photo.jpg

# Semantic swatches
colorthief-cli swatches photo.jpg
```

### Output formats

```bash
# Default: ANSI color swatches
colorthief-cli photo.jpg
# ▇▇ #e84393

# JSON with full color data
colorthief-cli photo.jpg --json

# CSS custom properties
colorthief-cli palette photo.jpg --css
# :root {
#     --color-1: #e84393;
#     --color-2: #6c5ce7;
# }
```

### Options

```bash
colorthief-cli palette photo.jpg --count 5        # Number of colors (2-20)
colorthief-cli photo.jpg --quality 1              # Sampling quality (1=best)
colorthief-cli photo.jpg --color-space rgb        # Color space (rgb or oklch)
colorthief-cli photo.jpg --region 0,0.66,1,0.34   # Sample the bottom third
```

`--region` takes `x,y,width,height` as fractions of the image size (0–1), the
same as the `region` option.

Stdin is supported — use `-` or pipe directly:

```bash
cat photo.jpg | colorthief-cli -
```

Multiple files are supported. Output is prefixed with filenames, and `--json` wraps
results in an object keyed by filename.

> **Note:** If you already have `colorthief` and `sharp` installed in a project, you
> can also use `colorthief` directly as the command name (without the `-cli` suffix).

## Links

- [Demo page & live examples](https://lokeshdhakar.com/projects/color-thief/)
- [GitHub](https://github.com/lokesh/color-thief)
- [npm](https://www.npmjs.com/package/colorthief)

## Contributing

```bash
npm run build          # Build all dist formats
npm run test           # Run all tests (Mocha + Cypress)
npm run test:node      # Node tests only
npm run test:browser   # Browser tests (requires npm run dev)
npm run dev            # Start local server on port 8080
```

## Releasing

```bash
# 1. Make sure you're on master with a clean working tree
git status

# 2. Run the full test suite
npm run build
npm run test:node
npm run test:browser   # requires npm run dev in another terminal

# 3. Preview what will be published
npm pack --dry-run

# 4. Tag and publish
npm version <major|minor|patch>   # bumps version, creates git tag
npm publish                       # builds via prepublishOnly, then publishes
git push && git push --tags
```

## License

[MIT](LICENSE) - Lokesh Dhakar

---
_Source: https://npm.io/package/colorthief · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
