Color Thief

Grab colors from any image or video.

13,000+ GitHub stars · 7M+ annual npm downloads

by Lokesh Dhakar
v3 released March 2026

Completely rewritten in TypeScript. Zero browser dependencies. New API.

Try it yourself

Drag an image from your computer onto the drop zone below.

Tap an example image to see Color Thief in action.

Drag an image here
Drop it!
or click an example image

Examples

1

Get the dominant color

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)'
2

Get a color palette

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()));
Source image
3

Observe a video source

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();
Dominant
Palette
4

The color object

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.14
Source image
5

Color proportions

Each 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 });
6

Get semantic swatches

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(); }
Source image
7

Quality settings

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 pixel
Source image
8

Asynchronous methods

The 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 });
Source image

Getting started

The package name is colorthief (not color-thief).

Install

npm install colorthief

Browser

Script tag

Load 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>

ES module

<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>

With a bundler

import { getColor, getPalette } from 'colorthief';
const { getColor, getPalette } = require('colorthief');

Node.js

Node.js support requires the sharp image processing library as a peer dependency.

npm install sharp
import { 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()));

CLI

Quick start, no install needed:

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

$ colorthief-cli photo.jpg
#c94f6e
$ colorthief-cli palette photo.jpg --count 3
#c94f6e
#5a8fa3
#d4a853
$ colorthief-cli swatches photo.jpg
Vibrant #e84393
Muted #a0b4c0
DarkVibrant #8b1a3a
DarkMuted #4a5568
LightVibrant #f6a5c1
LightMuted #d4d8dc

Flags

--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.

API

Color Thief v3 exports standalone functions — no class instantiation needed. Every function comes in sync and async variants.

Functions

getColor(source, options?)
Returns: Promise<Color | null>

Extracts the single dominant color from an image. Returns a Color object, or null if extraction fails.

getColorSync(source, options?)
Returns: Color | null

Synchronous version of getColor(). Browser sources only — does not support Node.js, Web Workers, or AbortSignal.

getPalette(source, options?)
Returns: Promise<Color[] | null>

Extracts a multi-color palette. Returns an array of Color objects sorted by population (most dominant first), or null if extraction fails.

getPaletteSync(source, options?)
Returns: Color[] | null

Synchronous version of getPalette(). Browser sources only.

getSwatches(source, options?)
Returns: Promise<SwatchMap>

Extracts semantic swatches classified into six roles: Vibrant, Muted, DarkVibrant, DarkMuted, LightVibrant, and LightMuted. Uses colorCount: 16 internally for best classification results.

getSwatchesSync(source, options?)
Returns: SwatchMap

Synchronous version of getSwatches(). Browser sources only.

observe(source, options)

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.

configure(options)
Returns: void

Globally override the pixel loader and/or quantizer used by all extraction functions.

  • options.loader — Custom pixel decoder
  • options.quantizer — Custom quantization algorithm (must call init() first)
createColor(r, g, b, population, proportion?)
Returns: Color

Factory function to manually create a Color object from RGB values. Useful for building Color objects from data you already have.

  • r, g, b — RGB values (0–255)
  • population — Pixel count for this color
  • proportion — Fraction of total pixels (0–1). Default: 0

Options

All 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.

Color object

Every extracted color is a rich object with format conversions, accessibility metadata, and WCAG contrast ratios.

Methods

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.

Properties

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.23

Swatches

getSwatches() 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.

SwatchMap

const swatches = getSwatchesSync(img); // { // Vibrant: Swatch | null, // Muted: Swatch | null, // DarkVibrant: Swatch | null, // DarkMuted: Swatch | null, // LightVibrant: Swatch | null, // LightMuted: Swatch | null, // }

Swatch object

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(); }

Swatch roles

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.

observe()

Reactively watches a live source and fires a callback with a fresh palette on every frame or change.

observe(source, options)
Returns: ObserveController

source — An HTMLVideoElement, HTMLCanvasElement, or HTMLImageElement.

options.onChangeRequired. 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.

ObserveController

Method Description
stop() Stops observing and cleans up all event listeners and animation frames. Always call this when done.

Behavior by source type

  • HTMLVideoElement — Extracts from the current frame on each requestAnimationFrame (throttled). Only runs while the video is playing. Also fires on seeked.
  • HTMLCanvasElement — Polls on each requestAnimationFrame (throttled).
  • HTMLImageElement — Extracts immediately if loaded, then watches for 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();

Source types

The source parameter accepts different types depending on the environment.

Browser

  • HTMLImageElement
  • HTMLCanvasElement
  • HTMLVideoElement
  • ImageData
  • ImageBitmap
  • OffscreenCanvas

Node.js

Requires the sharp library as a peer dependency.

  • string — file path
  • Buffer

Note: The sync API (getColorSync, getPaletteSync, getSwatchesSync) only accepts browser sources. Use the async API for Node.js.

License

Color Thief is licensed under the MIT License.