Color Thief
Grab colors from any image or video.
13,000+ GitHub stars · 7M+ annual npm downloads
by Lokesh DhakarCompletely rewritten in TypeScript. Zero browser dependencies. New API.
Grab colors from any image or video.
13,000+ GitHub stars · 7M+ annual npm downloads
by Lokesh DhakarCompletely rewritten in TypeScript. Zero browser dependencies. New API.
Drag an image from your computer onto the drop zone below.
Tap an example image to see Color Thief in action.
Extract the single dominant color from an image.
import { getColorSync } from 'colorthief';
const color = getColorSync(img);
color.hex(); // '#e84393'
color.css(); // 'rgb(232, 67, 147)'Extract a multi-color palette. Each color in the palette is a full Color object.
import { getPaletteSync } from 'colorthief';
const palette = getPaletteSync(img, { colorCount: 8 });
palette.forEach(c => console.log(c.hex()));Reactively watch a video source and get palette updates on every frame. Works with <video>, <canvas>, and <img> elements.
import { observe } from 'colorthief';
const controller = observe(videoElement, {
throttle: 200,
colorCount: 5,
onChange(palette) {
const [dominant] = palette;
document.body.style.background = dominant.css();
},
});
// Later — clean up
controller.stop();Every extracted color is a rich object with format conversions, accessibility metadata, and contrast ratios.
import { getColorSync } from 'colorthief';
const color = getColorSync(img);
color.rgb() // { r: 232, g: 67, b: 147 }
color.hex() // '#e84393'
color.hsl() // { h: 331, s: 77, l: 59 }
color.oklch() // { l: 0.64, c: 0.21, h: 353 }
color.css() // 'rgb(232, 67, 147)'
color.css('oklch') // 'oklch(0.64 0.21 353)'
color.textColor // '#ffffff' or '#000000'
color.isDark // true
color.contrast.white // 3.42
color.contrast.black // 6.14Each color in a palette includes a proportion property (0–1) showing what fraction of the image it represents.
const palette = getPaletteSync(img, { colorCount: 8 });
palette.forEach(color => {
console.log(color.hex(), color.proportion); // e.g. "#4a6741" 0.32
});Classify palette colors into six semantic roles. Each swatch includes text color recommendations.
import { getSwatchesSync } from 'colorthief';
const swatches = getSwatchesSync(img);
if (swatches.Vibrant) {
header.style.background = swatches.Vibrant.color.css();
header.style.color = swatches.Vibrant.titleTextColor.css();
}
if (swatches.DarkMuted) {
sidebar.style.background = swatches.DarkMuted.color.css();
sidebar.style.color = swatches.DarkMuted.bodyTextColor.css();
}The quality option controls how many pixels are sampled. Lower values sample more pixels (slower, more accurate). Default is 10.
import { getPaletteSync } from 'colorthief';
getPaletteSync(img, { quality: 1 }); // Every pixel
getPaletteSync(img, { quality: 10 }); // Every 10th pixel (default)
getPaletteSync(img, { quality: 50 }); // Every 50th pixelThe async API keeps your UI responsive during extraction. It breaks the work into chunks and pauses between them, giving the browser time to handle animations, scrolling, and clicks. Pass worker: true to offload quantization to a Web Worker thread entirely (browser-only). This is helpful when processing many images at once or working with very large images.
import { getColor, getPalette } from 'colorthief';
const color = await getColor(img);
const palette = await getPalette(img, { colorCount: 6 });
// Offload to Web Worker (browser only)
const workerPalette = await getPalette(img, { colorCount: 6, worker: true });The package name is colorthief (not color-thief).
npm install colorthiefLoad the UMD build from a CDN. This exposes a global ColorThief object.
<script src="https://unpkg.com/colorthief@3/dist/umd/color-thief.global.js"></script>
<script>
const color = ColorThief.getColorSync(img);
console.log(color.hex());
</script><script type="module">
import { getColorSync } from 'https://unpkg.com/colorthief@3/dist/index.js';
const img = document.querySelector('img');
img.addEventListener('load', () => {
const color = getColorSync(img);
console.log(color.hex());
});
</script>import { getColor, getPalette } from 'colorthief';const { getColor, getPalette } = require('colorthief');Node.js support requires the sharp image processing library as a peer dependency.
npm install sharpimport { getColor, getPalette } from 'colorthief';
const color = await getColor('photo.jpg');
console.log(color.hex());
const palette = await getPalette('photo.jpg', { colorCount: 5 });
palette.forEach(c => console.log(c.hex()));Quick start, no install needed:
npx colorthief-cli photo.jpgThe colorthief-cli package bundles everything needed (including sharp for image decoding), so it works immediately with no extra setup.
$ colorthief-cli photo.jpg
$ colorthief-cli palette photo.jpg --count 3
$ colorthief-cli swatches photo.jpg
--json # Full color data as JSON
--css # CSS custom properties
--count 5 # Number of palette colors (2-20)
--quality 1 # Sampling quality (1 = every pixel)
--color-space rgb # Quantization space (rgb or oklch)Stdin is supported (cat photo.jpg | colorthief-cli -), and multiple files can be passed at once. If you already have colorthief and sharp installed, you can use colorthief as the command name directly.
Color Thief v3 exports standalone functions — no class instantiation needed. Every function comes in sync and async variants.
Extracts the single dominant color from an image. Returns a Color object, or null if extraction fails.
Synchronous version of getColor(). Browser sources only — does not support Node.js, Web Workers, or AbortSignal.
Extracts a multi-color palette. Returns an array of Color objects sorted by population (most dominant first), or null if extraction fails.
Synchronous version of getPalette(). Browser sources only.
Extracts semantic swatches classified into six roles: Vibrant, Muted, DarkVibrant, DarkMuted, LightVibrant, and LightMuted. Uses colorCount: 16 internally for best classification results.
Synchronous version of getSwatches(). Browser sources only.
Reactively watches a video, canvas, or image element and fires a callback with a fresh palette on every frame or change. See observe() for full details.
Globally override the pixel loader and/or quantizer used by all extraction functions.
init() first)Factory function to manually create a Color object from RGB values. Useful for building Color objects from data you already have.
0All extraction functions accept an options object. The async API supports every option; the sync API omits signal, worker, and loader.
| Option | Type | Default | Description |
|---|---|---|---|
colorCount |
number | 10 |
Number of colors in the palette (2–20). Used by getPalette and getSwatches. |
quality |
number | 10 |
Pixel sampling rate. 1 samples every pixel (highest quality, slowest). 10 samples every 10th pixel. |
colorSpace |
string | 'oklch' |
'oklch' or 'rgb'. OKLCH produces more perceptually uniform palettes. |
ignoreWhite |
boolean | true |
Skip pixels that appear white during sampling. |
whiteThreshold |
number | 250 |
RGB channel value (0–255) above which a pixel is considered white. |
alphaThreshold |
number | 125 |
Alpha value (0–255) below which a pixel is considered transparent and skipped. |
minSaturation |
number | 0 |
Minimum HSV saturation (0–1). Pixels below this saturation are skipped. |
signal |
AbortSignal | — | Cancel a running extraction. Async API only. |
worker |
boolean | false |
Offload quantization to a Web Worker thread. Browser async API only. |
Every extracted color is a rich object with format conversions, accessibility metadata, and WCAG contrast ratios.
| Method | Returns | Description |
|---|---|---|
rgb() |
{ r, g, b } |
RGB values, each 0–255. |
hex() |
string |
Hex string, e.g. '#e84393'. |
hsl() |
{ h, s, l } |
Hue 0–360, saturation 0–100, lightness 0–100. |
oklch() |
{ l, c, h } |
Lightness 0–1, chroma 0–0.4, hue 0–360. |
css(format?) |
string |
CSS color string. Format: 'rgb' (default), 'hsl', or 'oklch'. |
array() |
[r, g, b] |
RGB tuple as a three-element array. |
toString() |
string |
Hex string. Allows direct use in template literals. |
| Property | Type | Description |
|---|---|---|
textColor |
string |
'#ffffff' or '#000000' — the recommended foreground text color for readability. |
isDark |
boolean |
true if the color is perceptually dark (relative luminance ≤ 0.179). |
isLight |
boolean |
true if the color is perceptually light. |
contrast |
ContrastInfo |
WCAG contrast ratios. Contains white (number), black (number), and foreground (Color) — a Color object for readable text. |
population |
number |
Relative pixel count from the quantizer. Higher values mean more dominant. |
proportion |
number |
Fraction of total sampled pixels (0–1). |
const color = getColorSync(img);
// Format conversions
color.hex(); // '#e84393'
color.rgb(); // { r: 232, g: 67, b: 147 }
color.hsl(); // { h: 331, s: 77, l: 59 }
color.oklch(); // { l: 0.64, c: 0.21, h: 353 }
color.css(); // 'rgb(232, 67, 147)'
color.css('oklch'); // 'oklch(0.64 0.21 353)'
// Accessibility
color.textColor; // '#ffffff'
color.isDark; // true
color.contrast.white; // 3.42
color.contrast.black; // 6.14
color.contrast.foreground.hex(); // '#ffffff'
// Population
color.population; // 14832
color.proportion; // 0.23getSwatches() and getSwatchesSync() return a SwatchMap — an object with six keys, one for each semantic role. Each value is either a Swatch or null if no color matched that role.
const swatches = getSwatchesSync(img);
// {
// Vibrant: Swatch | null,
// Muted: Swatch | null,
// DarkVibrant: Swatch | null,
// DarkMuted: Swatch | null,
// LightVibrant: Swatch | null,
// LightMuted: Swatch | null,
// }| Property | Type | Description |
|---|---|---|
color |
Color |
The extracted color for this role. |
role |
string |
The semantic role: 'Vibrant', 'Muted', 'DarkVibrant', 'DarkMuted', 'LightVibrant', or 'LightMuted'. |
titleTextColor |
Color |
Recommended color for title text over this swatch. |
bodyTextColor |
Color |
Recommended color for body text over this swatch. |
const swatches = getSwatchesSync(img);
if (swatches.Vibrant) {
document.body.style.background = swatches.Vibrant.color.css();
document.body.style.color = swatches.Vibrant.bodyTextColor.css();
title.style.color = swatches.Vibrant.titleTextColor.css();
}
if (swatches.DarkMuted) {
sidebar.style.background = swatches.DarkMuted.color.css();
sidebar.style.color = swatches.DarkMuted.bodyTextColor.css();
}| Role | Description |
|---|---|
Vibrant |
Bright, saturated colors. Good for primary accents. |
Muted |
Desaturated, medium brightness. Good for backgrounds. |
DarkVibrant |
Saturated and dark. Good for status bars, headers. |
DarkMuted |
Desaturated and dark. Good for text backgrounds. |
LightVibrant |
Saturated and light. Good for highlights, hover states. |
LightMuted |
Desaturated and light. Good for card backgrounds, borders. |
Reactively watches a live source and fires a callback with a fresh palette on every frame or change.
source — An HTMLVideoElement, HTMLCanvasElement, or HTMLImageElement.
options.onChange — Required. Callback fired with (palette: Color[]) => void on each update.
options.throttle — Minimum milliseconds between updates. Default: 200.
options.colorCount — Number of colors (2–20). Default: 5.
options.quality — Pixel sampling rate. Default: 10.
options.colorSpace — 'oklch' or 'rgb'. Default: 'oklch'.
Also accepts all filter options: ignoreWhite, whiteThreshold, alphaThreshold, minSaturation.
| Method | Description |
|---|---|
stop() |
Stops observing and cleans up all event listeners and animation frames. Always call this when done. |
requestAnimationFrame (throttled). Only runs while the video is playing. Also fires on seeked.requestAnimationFrame (throttled).src/srcset attribute changes via MutationObserver. Also listens for the load event.const controller = observe(videoElement, {
throttle: 200,
colorCount: 5,
onChange(palette) {
const [dominant] = palette;
document.body.style.background = dominant.css();
header.style.color = dominant.textColor;
},
});
// Later — clean up
controller.stop();The source parameter accepts different types depending on the environment.
HTMLImageElementHTMLCanvasElementHTMLVideoElementImageDataImageBitmapOffscreenCanvasRequires the sharp library as a peer dependency.
string — file pathBufferNote: The sync API (getColorSync, getPaletteSync, getSwatchesSync) only accepts browser sources. Use the async API for Node.js.
Color Thief is licensed under the MIT License.
The Color Thief package includes multiple distribution files to support different environments and build processes. Here is the list of all the files in the /dist folder and what formats they support:
$ npm i --save colorthief
const ColorThief = require('colorthief');
getColor() and getPalette() methods return a Promise when used in Node.
const img = resolve(process.cwd(), 'rainbow.png');
ColorThief.getColor(img)
.then(color => { console.log(color) })
.catch(err => { console.log(err) })
ColorThief.getPalette(img, 5)
.then(palette => { console.log(palette) })
.catch(err => { console.log(err) })
There are multiples ways to install Color Thief when using it in the browser:
$ npm i --save colorthief
<script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.7.0/color-thief.umd.js"></script>
<script src="node_modules/colorthief/dist/color-thief.umd.js"></script>
<script>
const colorThief = new ColorThief();
const img = document.querySelector('img');
// Make sure image is finished loading
if (img.complete) {
colorThief.getColor(img);
} else {
img.addEventListener('load', function() {
colorThief.getColor(img);
});
}
</script>
<script type="module" src="app.js"></script>
import ColorThief from './node_modules/colorthief/dist/color-thief.mjs'
const colorThief = new ColorThief();
const img = document.querySelector('img');
if (img.complete) {
colorThief.getColor(img);
} else {
img.addEventListener('load', function() {
colorThief.getColor(img);
});
}
All questions are about the usage of Color Thief in the browser, not Node.
Yes. As of v2.7.0, Color Thief will throw a descriptive error if the image hasn't loaded: Image has not finished loading.
// BETTER
const colorThief = new ColorThief();
const img = document.querySelector('img');
if (img.complete) {
colorThief.getColor(img);
} else {
img.addEventListener('load', function() {
colorThief.getColor(img);
});
}
Yes, if CORS is configured. Set access-control-allow-origin on the server and add crossorigin="anonymous" to the image element.
<img src="https://example.com/image.jpg" crossorigin="anonymous" />
In v2, colors are returned as [r, g, b] arrays. Convert with:
const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => {
const hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex
}).join('')
rgbToHex(102, 51, 153); // #663399
Tip: In v3, just use color.hex() — no conversion needed.
color-thief tag.Color Thief is licensed under the MIT License.