Skip to content

Latest commit



1902 lines (1279 loc) · 78.9 KB

File metadata and controls

1902 lines (1279 loc) · 78.9 KB
layout title codebase
API Reference
API table of contents

Colors are plain objects

Culori does not have a Color class. Instead, it uses plain objects to represent colors:

/* A RGB color */
  mode: 'rgb',
  r: 0.1,
  g: 0.2,
  b: 1,
  alpha: 1

The object needs to have a mode property that identifies the color space, and values for each channel in that particular color space (see the Color Spaces page for the channels and ranges expected for each color space). Optionally, the alpha property is used for the color's alpha channel.

Parsing and conversion

# parse(string) → color or undefined


Parses a string and returns the corresponding color. The color will be in the matching color space, e.g. RGB for hex strings, HSL for hsl(…, …, …) strings, et cetera. If no built-in parsers can match the string, the function will return undefined.

import { parse } from 'culori';

/* A named color */
// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' }

/* A hex color */
// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' }

/* A HSL color */
parse('hsl(60 50% 10% / 100%)');
// ⇒ { h: 60, s: 0.5, b: 0.1, alpha: 1, mode: 'hsl' }

/* A Lab color */
parse('lab(100% -50 50)');
// ⇒ { l: 100, a: -50, b: 50, mode: 'lab' }

In most cases, instead of using parse() directly (which only operates on strings), you'll want to use a converter(), which accepts strings and color objects and returns objects in a predictable color space.

# converter(mode = "rgb") → function (color or String)


Returns a converter: a function that can convert any color to the mode color space.

import { converter } from 'culori';

let rgb = converter('rgb');
let lab = converter('lab');

// ⇒ { mode: "rgb", r: 0.49…, g: 0.49…, b: 0.49… }

// ⇒ { mode: "lab", l: 94.79…, a: 0, b: 0 }

Converters accept either strings (which will be parsed with parse() under the hood) or color objects. If the mode key is absent from the color object passed to a converter, it's assumed to be in the converter's color space.


These methods serialize colors to strings, in various formats.

# formatHex(color or string) → string


Returns the hex string for the given color. The color's alpha channel is omitted, and the red, green, and blue channels are clamped to the the interval [0, 255], i.e. colors that are not displayable are serialized as if they'd been passed through the clampRgb method.

import { formatHex } from 'culori';

// ⇒ "#ff0000"

# formatHex8(color or string) → string


Returns the 8-character hex string for the given color. The red, green, blue, and alpha channels are clamped to the the interval [0, 255], i.e. colors that are not displayable are serialized as if they'd been passed through the clampRgb method.

import { formatHex8 } from 'culori';

formatHex8({ mode: 'rgb', r: 1, g: 0, b: 0, alpha: 0.5 });
// ⇒ "#ff000080"

# formatRgb(color or string) → string


Returns the rgb(…) / rgba(…) string for the given color. Fully opaque colors will be serialized as rgb(), and semi-transparent colors as rgba(), in accordance with the CSSOM standard serialization. Like in the case of formatHex, the red, green, and blue channels are clamped to the interval [0, 255].

import { formatRgb } from 'culori';

formatRgb('lab(50 0 0 / 25%)');
// ⇒ "rgba(119, 119, 119, 0.25)"

# formatHsl(color or string) → string


Returns the hsl(…) / hsla(…) string for the given color. Fully opaque colors will be serialized as hsl(), and semi-transparent colors as hsla(). All values are rounded to a precision of two digits. The Saturation and Lightness are clamped to the interval [0%, 100%].

import { formatHsl } from 'culori';

formatHsl('lab(50 0 0 / 25%)');
// ⇒ 'hsla(194.33, 0%, 46.63%, 0.25)'

# formatCss(color or string) → string


Returns a CSS string for the given color, based on the CSS Color Level 4 specification. A few color spaces, such as hsl or lab, have their own functional representation in CSS. We use that whenever possible; the hsl color space is represented as hsl(h% s l / alpha). Predefined color spaces are represented using the color() notation with the appropriate identifier for the color space, e.g. color(display-p3 r g b / alpha). All other colors paces use the color() notation with a dashed identifier. For example, jab is represented as color(--jzazbz j a b / alpha).

You can find the exact string produced for each color space under the Serialized as entry on the Color Spaces page.

Channel values are serialized as-is, with no change in the precision. To avoid compatibility issues, sRGB colors are represented as color(srgb r g b / alpha) rather than rgb(r, g, b, alpha). For the latter, use the formatRgb() method instead.

An alpha of exactly 1 is omitted from the representation.

Note: The strings returned by these methods are not widely supported in current browsers and should not be used in CSS as-is.

import { formatCss } from 'culori';

	A mode with its own function notation.
formatCss({ mode: 'hsl', h: 30, s: 1, l: 0.5, alpha: 0.5 });
// ⇒ 'hsl(30 100% 50% / 0.5)'

	A predefined color space.
formatCss({ mode: 'p3', r: 0.5, s: 0.25, b: 1, alpha: 1 });
// ⇒ 'color(display-p3 0.5 0.25 1)'

	sRGB colors.
formatCss({ mode: 'rgb', r: 0.5, s: 0.25, b: 1, alpha: 0.25 });
// ⇒ 'color(srgb 0.5 0.25 1 / 0.25)'

	A custom color space.
formatCss({ mode: 'lrgb', r: 0.5, s: 0.25, b: 1, alpha: 0.25 });
// ⇒ 'color(--srgb-linear 0.5 0.25 1 / 0.25)'

Gamut mapping

Some color spaces (Lab and LCh in particular) allow you to express colors that can't be displayed on-screen. The methods below allow you to identify when that's the case and to produce displayable versions of the colors.

# inGamut(mode = "rgb") → function (color | string)


Given a color space, returns a function with which to check whether a particular color is within the gamut of that color space.

This is meant to be used with RGB-based color spaces and their derivates (hsl, hsv, etc.). If the color space has no gamut limits, the function will always return true, regardless of the color passed to it. To find out which color spaces have gamut limits, see the Color Spaces page.

import { inGamut } from 'culori';

const inRgb = inGamut('rgb');

// ⇒ true

inRgb('color(srgb 1.1 0 0)');
// ⇒ false

# displayable(color or string) → boolean


Checks whether a particular color fits inside the sRGB gamut. Equivalent to inGamut('rgb').

import { displayable } from 'culori';

// ⇒ true

displayable('color(srgb 1.1 0 0)');
// ⇒ false

# clampRgb(color or string) → color


Obtains a displayable version of the color by clamping the r, g, b channel values of the color's RGB representation to the interval [0, 1]. The returned color is in the same color space as the original color.

This is the faster, simpler, way to make a color displayable. It's what browsers do when you use a CSS color whose channels exceed the gamut. For example, rgb(300 100 200) is interpreted as rgb(255 100 200).

Because clamping individual red, green, and blue values independently can alter their proportions in the final color, it often changes the color's hue.

import { clampRgb } from 'culori';

// RGB clamping:
clampRgb('lab(50% 100 100)');
// ⇒ { mode: "lab", l: 54.29…, a: 80.81…, b: 69.88… }

# clampGamut(mode = 'rgb') → function(color | string)

This function extends the functionality of clampRgb to other color spaces. Given a color space, it returns a function with which to obtain colors within the gamut of that color space.

If the color space has no gamut limits, colors are returned unchanged. To find out which color spaces have gamut limits, see the Color Spaces page.

The in-gamut color is always returned in the color space of the original color.

import { formatCss, clampGamut } from 'culori';

const crimson = 'color(display-p3 0.8 0.1 0.3)';
const toRgb = clampGamut('rgb');

// ⇒ 'color(display-p3 0.801… 0.169… 0.302…)'


# clampChroma(color or string, mode = 'lch', rgbGamut = 'rgb') → color


Obtains a displayable version of the color by converting it to a temporary color space containing a Chroma channel, then looking for the closest Chroma value that's displayable for the given Lightness and Hue. Compared to clampRgb, the function has the advantage of preserving the hue of the original color. The displayable color returned by this function will be converted back to the original color space.

import { clampChroma } from 'culori';

clampChroma('lab(50% 100 100)');
// ⇒ { mode: 'lab', l:50.00…, a: 63.11…, b: 63.11… }

By default, the color is converted to lch to perform the clamping, but any color space that contains a Chroma dimension can be used by sending an explicit mode argument.

Likewise, the destination RGB gamut can be overriden with the corresponding parameter.

import { clampChroma } from 'culori';

clampChroma({ mode: 'oklch', l: 0.5, c: 0.16, h: 180 }, 'oklch');
// ⇒ { mode: 'oklch', l: 0.5, c: 0.09, h: 180 }

In general, chroma clamping is more accurate and computationally simpler when performed in the color's original space, where possible. Here's some sample code that uses the color's own mode for color spaces containing a Chroma dimension, and lch otherwise:

import { clampChroma } from 'culori';

clampChroma(color, color.c !== undefined ? color.mode : 'lch');

If the chroma-finding algorithm fails to find a displayable color (which can happen when not even the achromatic version, with Chroma = 0, is displayable), the method falls back to the clampRgb method, as a last resort.

The function uses the bisection method to speed up the search for the largest Chroma value. However, due to discontinuities in the CIELCh color space, the function is not guaranteed to return the optimal result. See this discussion for details.

# toGamut(dest = 'rgb', mode = 'oklch', delta = differenceEuclidean('oklch'), jnd = 0.02) → function (color | string)


Obtain a color that's in the dest gamut, by first converting it to the mode color space and then finding the largest chroma that's in gamut, similar to clampChroma().

The color returned is in the dest color space.

import { p3, toGamut } from 'culori';

const color = 'lch(80% 150 60)';

// ⇒ { mode: "p3", r: 1.229…, g: 0.547…, b: -0.073… }

const toP3 = toGamut('p3');
// ⇒ { mode: "p3", r: 0.999…, g: 0.696…, b: 0.508… }

To address the shortcomings of clampChroma, which can sometimes produce colors more desaturated than necessary, the test used in the binary search is replaced with “is color is roughly in gamut”, by comparing the candidate to the clipped version (obtained with clampGamut). The test passes if the colors are not to dissimilar, judged by the delta color difference function and an associated jnd just-noticeable difference value.

The default arguments for this function correspond to the gamut mapping algorithm defined in the CSS Color Module Level 4 spec, but the algorithm itself is slightly different.

The “roughly in gamut” aspect of the algorithm can be disabled by passing null for the delta color difference function:

import { toGamut } from 'culori';
const clampToP3 = toGamut('p3', 'oklch', null);


In any color space, colors occupy positions given by their values for each channel. Interpolating colors means tracing a line through the coordinates of these colors, and figuring out what colors reside on the line at various positions.

red and blue, linearly interpolated

Above is the path between red and blue in the RGB color space. Going from left to right, we start at red and steadily blend in more and more blue as we progress, until the color is fully blue at destination. This is a linear interpolation between two colors.

# interpolate(colors, mode = "rgb", overrides)


Returns an interpolator in the mode color space for an array of colors. The interpolator is a function that accepts a value t in the interval [0, 1] and returns the interpolated color in the mode color space.

The colors in the array can be in any color space, or they can even be strings.

import { interpolate } from 'culori';

let grays = interpolate(['#fff', '#000']);
// ⇒ { mode: 'rgb', r: 0.5, g: 0.5, b: 0.5 }

By default, colors in all spaces are interpolated linearly across all channels. You can override the way specific channels are interpolated with the overrides object, the third argument of interpolate().

import { interpolate, interpolatorSplineBasis } from 'culori';

let my_interpolator = interpolate(['blue', 'red'], 'lch', {
	// spline instead of linear interpolation:
	h: interpolatorSplineBasis

There are a few interpolation methods available, listed below. Depending on the channel, the numeric values can be interpreted/interpolated in various modes. The hue channel, for example, is interpolated by taking into account the shortest path around the hue circle (fixupHue). And the fixupAlpha mode assumes an undefined alpha is 1.

Color stop positions

You can specify positions of color stops to interpolate in the way they're defined in the CSS Images Module Level 4 specification:

import { interpolate } from 'culori';

interpolate(['red', ['green', 0.25], 'blue']);

In the image below, you can see the effect of interpolating with evenly-spaced colors (1) vs. positioned colors stops (2):

![Evenly spaced vs. positions]({{"/img/evenly-spaced-vs-positions.png" | url }})

To specify a positioned color stop, use an array that contains the color followed by its position. The color stops should be specified in ascending order.

For omitted (implicit) positions, we apply the rules from the spec:

  1. if the first color doesn't have a position, it's assumed to be 0; if the last color doesn't have a position, it's assumed to be 1;
  2. any other color stops that don't have a position will be evenly distributed along the gradient line between the positioned color stops.

Easing functions

You can add easing functions between any two colors in the array:

import { interpolate } from 'culori';

const easeIn = t => t * t;
interpolate(['red', easeIn, 'green']);

Any function in the colors array will be interpreted as an easing function, which is (for our purposes), a function that takes an argument t ∈ [0, 1] and returns a value v ∈ [0, 1].

To apply the same easing function between all color pairs, instead of individual ones, add the easing as the first element in the array:

import { interpolate } from 'culori';

const easeIn = t => t * t;

// this form:
interpolate([easeIn, 'red', 'green', 'blue']);

// is equivalent to:
interpolate(['red', easeIn, 'green', easeIn, 'blue']);

The easing function can alternatively be applied the hard way:

import { interpolate, interpolatorPiecewise, lerp } from 'culori';

const easeIn = t => t * t;

	['red', 'green', 'blue'],
	interpolatorPiecewise((a, b, t) => lerp(a, b)(easeIn(t)))

This formula can be helpful if you wanted to apply a different easing function per channel:

import { interpolate, interpolatorPiecewise, lerp } from 'culori';
function piecewiseEasing(easingFn) {
	return interpolatorPiecewise((a, b, t) => lerp(a, b)(easingFn(t)));

interpolate(['red', 'green', 'blue'], 'rgb', {
	r: piecewiseEasing(easeIn),
	g: piecewiseEasing(easeOut),
	b: piecewiseEasing(easeInOut)

Culori comes with just a few easing functions, but you can find several online:

Interpolation hints

Any number in the colors array will be interpreted as an interpolation hint:

import { interpolate } from 'culori';

// interpolation hint:
interpolate(['red', 0.25, 'green']);

As opposed to how current browsers implement the CSS spec (see discussion), interpolation hints do not affect color stop positions in Culori.

Built-in easing functions

# easingMidpoint(H = 0.5)


Proposed here, the midpoint easing function lets you shift the midpoint of a gradient like in tools such as Adobe Photoshop. You can use it with interpolate() as an alternative to interpolation hints:

import { interpolate, easingMidpoint } from 'culori';
// Explicit midpoint easing:
interpolate(['red', easingMidpoint(0.25), 'blue']);

// equivalent to:
interpolate(['red', 0.25, 'blue']);

# easingSmoothstep


The Smoothstep easing function.

# easingSmoothstep


The inverse of the Smoothstep easing function.

# easingSmootherstep


Smootherstep is a variant of the Smoothstep easing function.

# easingInOutSine


Sinusoidal in-out easing. Can be used to create, for example, a cosine interpolation as described by Paul Bourke:

import { interpolate, easingInOutSine } from 'culori';
interpolate([easingInOutSine, 'red', 'green', 'blue']);

# easingGamma(γ = 1) → function(t)


The gamma easing.

import { samples, easingGamma } from 'culori';
// ⇒ [0, 0.0625, 0.25, 0.5625, 1]

Interpolation methods

You'll use these methods when you want to override how colors get interpolated in a specific color space, or when defining the default interpolation for custom color spaces.

# interpolatorLinear(values)


A linear interpolator for values in a channel.

Basis splines

Basis splines (also called B-splines) are available in the following variants:

# interpolatorSplineBasis(values)


A basis spline which uses one-sided finite differences for the slopes at the boundaries.

# interpolatorSplineBasisClosed(values)


A basis spline which considers the values array to be periodic.

Natural splines

Natural interpolating splines are related to basis splines, as explained in this handout by Kirby A. Baker (sections 4 and 5).

# interpolatorSplineNatural(values)


A natural spline which uses one-sided finite differences for the slopes at the boundaries.

# interpolatorSplineNaturalClosed(values)


A natural spline which considers the values array to be periodic.

Monotone splines

The monotone splines are based on the following paper (via d3-shape):

Steffen, M. "A simple method for monotonic interpolation in one dimension." in Astronomy and Astrophysics, Vol. 239, p. 443-450 (Nov. 1990), Provided by the SAO/NASA Astrophysics Data System.

The following variants are available:

# interpolatorSplineMonotone(values)


A monotone spline that uses one-sided finite differences to find the slopes at the boundaries.

# interpolatorSplineMonotone2(values)


A monotone spline for which we derive the slopes at the boundaries by tracing a parabola through the first/last three values.

# interpolatorSplineMonotoneClosed(values)


A monotone spline which considers the values array to be periodic.

Custom piecewise interpolation

# interpolatorPiecewise(interpolator) src/interpolate/piecewise.js

Use a custom piecewise interpolator function in the form function (a, b, t) => value:

import { interpolate, interpolatorPiecewise } from 'culori';

let linear = (a, b, t) => (1 - t) * a + t * b;
interpolate(['red', 'green'], interpolatorPiecewise(linear));

When one of the two values to be interpolated is undefined, it will mirror the defined value: [undefined, b] becomes [b, b]. If both values are undefined, they are left as-is.

The interpolatorLinear() function uses interpolatorPiecewise() under the hood.

Interpolation Fixup

By default, channel values that need to be interpolated are treated as normal numbers. However, for some channels, the values hold special singificance and can be fixed up before interpolation for better results.

Hue fixup

Hue is a circular value, so there are two directions in which to interpolate between two hues (clockwise and anti-clockwise). The functions below take an array of hues and adjusts them to impose a certain interpolation direction while maintaining the absolute difference between consecutive hues.

Adjusted hues will not necessarily be in the [0, 360) interval. All fixup methods leave undefined values, and the values immediately following them, unaltered. The names of the methods come from this discussion.

# fixupHueShorter(values) → Array


Adjusts the hues so that values are interpolated along the shortest path around the hue circle.

This is the default in all built-in color spaces using a hue channel. Below is an extract from the definition of the HSL color space:

/* --- hsl/definition.js --- */
export default {
	// ...
	interpolate: {
		h: {
			use: interpolatorLinear,
			fixup: fixupHueShorter
		s: interpolatorLinear,
		l: interpolatorLinear,
		alpha: {
			use: interpolatorLinear,
			fixup: fixupAlpha
	// ...

To omit the fixup and treat hues as normal numbers, use a custom interpolation on the h channel, and overwrite the fixup function with an identity function:

import { interpolate } from 'culori';

let hsl_long = interpolate(['blue', 'red', 'green'], 'hsl', {
	h: {
		fixup: arr => arr

Treating the hues array as-is (with an identity function) corresponds to the specified fixup method in the CSSWG issue mentioned earlier.

# fixupHueLonger(values) → Array


Adjusts the hues so that they are interpolated along the longest path around the hue circle.

# fixupHueIncreasing(values) → Array


Adjusts the hues so that every hue is larger than the previous.

# fixupHueDecreasing(values) → Array


Adjusts the hues so that every hue is smaller than the previous.

Alpha fixup

# fixupAlpha(values) → Array


Turns all undefined values in the array to 1 (full opacity), unless all values in the array are undefined, in which case it leaves the values unaltered.

This is the default method for the alpha channel in all built-in color spaces.

Evenly-spaced samples

# samples(n = 2)


Returns an array of n equally-spaced samples in the [0, 1] range, with 0 and 1 at the ends.

import { samples } from 'culori';

// ⇒ [0, 0.5, 1]

// ⇒ [0, 0.25, 0.5, 0.75, 1]

The samples are useful for interpolate() to generate color scales:

import { samples, interpolate, formatHex } from 'culori';

let grays = interpolate(['#fff', '#000']);
// ⇒ ["#ffffff", "#bfbfbf", "#808080", "#404040", "#000000"]

As with the interpolate() method, you can map the samples through an easing function or scale to obtain a different distribution of the samples.

import { samples } from 'culori';
import easing from 'bezier-easing';

// Bezier easing:
let bezier = easing(0, 0, 1, 0.5);

// easeInQuad:
samples(10).map(t => t * t);


# lerp(a, b, t) → value


Interpolates between the values a and b at the point t ∈ [0, 1].

import { lerp } from 'culori';
lerp(5, 10, 0.5);
// ⇒ 7.5

# unlerp(a, b, v) → value


Returns the point t at which the value v is located between the values a and b. The inverse of lerp.

# blerp(a00, a01, a10, a11, tx, ty) → value


Perform the bilinear interpolation of the four values a00, a01, a10, and a11 at the point (tx, ty), with tx, ty ∈ [0, 1]. This is the extension of lerp to two dimensions.

# trilerp(a000, a010, a100, a110, a001, a011, a101, a111, tx, ty, tz) → value


Perform the trilinear interpolation of the eight values a000, a010, a100, a110, a001, a011, a101, and a111 at the point (tx, ty, tz), with tx, ty, tz ∈ [0, 1]. This is the extension of lerp to three dimensions.


# mapper(fn, mode = "rgb") → function (color | string)


Creates a mapping that applies fn on each channel of the color in the mode color space.

The resulting function accepts a single argument (a color object or a string), which it converts to the mode color space if necessary.

The mode parameter can be set to null, in which case the mapper will iterate through the channels in the color's original color space.

The fn callback has the following signature:

fn(value, channel, color, mode)


  • value is the current value;
  • channel is the current channel;
  • color is a reference to the entire color object;
  • mode is forwarded from the call to mapper.

Here's the implementation of alpha premultiplication:

import { mapper } from 'culori';

const multiplyAlpha = mapper((val, ch, color) => {
	if (ch !== 'alpha') {
		return (val || 0) / (color.alpha !== undefined ? color.alpha : 1);
	return val;
}, 'rgb');

multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 });
// ⇒ { mode: 'rgb', r: 0.5, g: 0.3, b: 0.2, a: 0.5 }

All channels, including alpha, are included in the mapping. You may want to handle the alpha channel differently in the callback function, like in the example above.

Returning undefined or NaN from the callback function will omit that channel from the resulting color object.

Built-in mappings

# mapAlphaMultiply


Multiplies the color's alpha value into all its other channels:

import { mapper, mapAlphaMultiply } from 'culori';

let multiplyAlpha = mapper(mapAlphaMultiply, 'rgb');
multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 });
// ⇒ { mode: 'rgb', r: 0.5, g: 0.3, b: 0.2, a: 0.5 }

Any undefined channel value will be considered to be 0 (zero), to enable alpha-premultiplied interpolation with achromatic colors in hue-based color spaces (HSL, LCh, etc.).

# mapAlphaDivide


Divides a color's other channels by its alpha value. It's the opposite of mapAlphaMultiply, and is used in interpolation with alpha premultiplication:

import { mapper, mapAlphaMultiply, mapAlphaDivide } from 'culori';

let multiplyAlpha = mapper(mapAlphaMultiply, 'rgb');
let divideAlpha = mapper(mapAlphaDivide, 'rgb');

divideAlpha(multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 }));
// ⇒ { mode: 'rgb', r: 1, g: 0.6, b: 0.4, a: 0.5 }

Any undefined channel value will be considered to be 0 (zero), to enable alpha-premultiplied interpolation with achromatic colors in hue-based color spaces (HSL, LCh, etc.).

# mapTransferLinear(slope = 1, intercept = 0)


# mapTransferGamma(amplitude = 1, exponent = 1, offset = 0)


Interpolating with mappings

# interpolateWith(premap, postmap)


Adds a pre-mapping and a post-mapping to an interpolation, to enable things like alpha premultiplication:

import { interpolateWith, mapAlphaMultiply, mapAlphaDivide } from 'culori';

let interpolateWithAlphaPremult = interpolateWith(

interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);

To chain more than one mapping:

import { interpolateWith, mapAlphaMultiply, mapAlphaDivide } from 'culori';

const mapChromaMultiply = (v, ch, c, mode) => {
	// ...

const mapChromaDivide = (v, ch, c, mode) => {
	// ...

let interpolateWithAlphaChromaPremult = interpolateWith(
	(...args) => mapChromaMultiply(mapAlphaMultiply(...args)),
	(...args) => mapAlphaDivide(mapChromaDivide(...args))

interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);

# interpolateWithPremultipliedAlpha(colors, mode = "rgb", overrides)


Takes the same arguments as interpolate(), but applies alpha premultiplication.

import { interpolate, interpolateWithPremultipliedAlpha } from 'culori';
let colors = ['red', 'transparent', 'blue'];

// alpha ignored for the R/G/B channels:
interpolate(colors, 'rgb');

// alpha premultiplied into the R/G/B channels:
interpolateWithPremultipliedAlpha(colors, 'rgb');

Color Difference

These methods are concerned to finding the distance between two colors based on various formulas. Each of these formulas will return a function (colorA, colorB) that lets you measure the distance between two colors.

Euclidean distance

# differenceEuclidean(mode = 'rgb', weights = [1, 1, 1, 0])


Returns a Euclidean distance function in a certain color space.

You can optionally assign different weights to the channels in the color space. See, for example, the Kotsarenko/Ramos distance or ΔEITP.

The default weights [1, 1, 1, 0] mean that the alpha, which is the fourth channel in all the color spaces Culori defines, is not taken into account. Send [1, 1, 1, 1] as the weights to include it in the computation.

In cylindrical spaces, the hue is factored into the Euclidean distance in a variety of ways. The functions below are used internally:

# differenceHueChroma(colorA, colorB)


Computes the hue contribution as the geometric mean of chord lengths belonging to the chromas of the two colors. This is the handling of hue in cylindrical forms of CIE-related color spaces: lch, lchuv, dlch, oklch, jch.

# differenceHueSaturation(colorA, colorB)


Computes the hue contribution as the geometric mean of chord lengths belonging to the saturations of the two colors. This is the handling of hue in the HSL / HSV / HSI family of color spaces.

# differenceHueNaive(colorA, colorB)


For remaining color spaces (HWB), we consider hues numbers, but apply a shortest path around the hue circle (analogous to fixupHueShorter). If you insist on using Euclidean distances on these spaces, you can use the weights to control the contribution of the hue difference towards the total difference.

CIE color difference formulas

All these color difference functions operate on the lab65 color space.

# differenceCie76()


Computes the CIE76 ΔE*ab color difference between the colors a and b. The function is identical to differenceEuclidean('lab65').

# differenceCie94(kL = 1, K1 = 0.045, K2 = 0.015)


Computes the CIE94 ΔE*94 color difference between the colors a and b.

# differenceCiede2000(Kl = 1, Kc = 1, Kh = 1)


Computes the CIEDE2000 ΔE*00 color difference between the colors a and b as implemented by G. Sharma.

# differenceCmc()


Computes the CMC l:c (1984) ΔE*CMC color difference between the colors a and b.

ΔE*CMC is not considered a metric since it's not symmetrical, that is the distance from a to b is not always equal to the distance from b to a. Therefore it cannot be reliably used with nearest().

# differenceHyab()


Computes the HyAB color difference between the colors a and b, as proposed in:

Abasi S, Amani Tehran M, Fairchild MD. Distance metrics for very large color differences. Color Res Appl. 2019; 1–16. (PDF)

The HyAB formula combines the Euclidean and city block distance and has been experimentally shown to work better for large color differences than CIEDE2000, while still holding up well for smaller color differences, making it a "good candidate formula for image processing and computer vision applications".

Other difference formulas

# differenceKotsarenkoRamos()


Computes the Kotsarenko/Ramos color difference between the colors a and b. This is a weighted Euclidean distance in the yiq color space.

# differenceItp()


Computes the ΔEITP color difference metric between the colors a and b. This is a weighted Euclidean distance in the itp color space, scaled by a factor of 720 so that a the just-noticeable difference (JND) corresponds to a value of 1.

Nearest color(s)

# nearest(colors, metric = differenceEuclidean(), accessor = identity) → function(color, n = 1, τ = Infinity)


Takes a colors array and a metric color difference formula, and returns a function with which you can find n colors nearest to color, with a maximum distance of τ. Use n = Infinity to get all colors in the array with a maximum distance of τ.

	Example: get three CSS named colors closest to any color
import { colorsNamed, nearest, differenceCiede2000 } from 'culori';

let colors = Object.keys(colorsNamed);
let nearestNamedColors = nearest(colors, differenceCiede2000());

nearestNamedColors('lch(50% 70 60)', 3);
// => ["chocolate", "sienna", "peru"]

By default, colors needs to be an array of color values. If your array contains something other than a simple color value, you can provide the accessor argument to point to the color value associated with each item in the array.

The example below shows a common data structure for a color palette: an object whose keys are the names and whose values are their associated color representations.

import { nearest, differenceEuclidean } from 'culori';

	Example: get the closest color from a palette
let palette = {
	Burgundy: '#914e72',
	Blue: '#0078bf',
	Green: '#00a95c',
	'Medium Blue': '#3255a4',
	'Bright Red': '#f15060'

let names = Object.keys(palette);

let nearestColors = nearest(
	name => palette[name]

nearestColors('red', 1);
// => ["Bright Red"]


Culori makes available the separable blend modes defined in the W3C Compositing and Blending Level 2 specification.

# blend(colors, type = 'normal', mode = 'rgb') → color


A separable blend mode is a simple formula that gets applied to each channel in the color space independently. The available blend modes are color-burn, color-dodge, darken, difference, exclusion, hard-light, lighten, multiply, normal, overlay, screen , and soft-light. They are designed to work on RGB colors, so mode is expected to be rgb or lrgb.

An example of blending three colors:

import { blend } from 'culori';

	['rgba(255, 0, 0, 0.5)', 'rgba(0, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)'],
// ⇒ { mode: 'rgb', alpha: 0.875, r: 0.57…, g: 0.57…, b:0.57… }

In addition to strings, the type parameter supports a function (b, s) → v that takes the values of the backdrop and source color to return the blended value. This allows you to write your own (separable) blending functions. For example, an average blending mode:

import { blend } from 'culori';

blend(['red', 'green'], function average(b, s) {
	return (b + s) / 2;

The non-separable blend modes — color, hue, saturation, and lightness — are not available. The effect which they mean to produce is better obtained with simple formulas on a cylindrical color space (e.g. HSL).

Average color

# average(colors, mode = 'rgb', overrides)


Returns the average color of the colors array, in the color space specified by the mode argument. The color is obtained by the arithmetic average of values on each individual channel.

Colors with undefined values on a channel don't participate in the average for that channel.

import { average } from 'culori';

average(['salmon', 'tomato'], 'lab');
// ⇒ { 'mode': 'lab', l: 65.41…, a: 53.00…, b: 39.01… }

# averageNumber(values)


The arithmetic mean of values in the values array.

# averageAngle(values)


The function used by default to average hue values in all built-in color spaces, using the formula for the mean of circular quantities.

Random colors

# random(mode = 'rgb', constraints = {})


Obtain a random color from a particular color space, with optional constraints. The resulting color will be in the color space from where it has been picked.

Basic usage:

import { random } from 'culori';

// ⇒ { mode: 'rgb', r: 0.75, g: 0.12, b: 0.99 }

Specifying constraints

Random colors are, by definition, all over the color space and not all of them will look particularly nice. Some color spaces, such as HSL or HSV, are also biased towards colors close to black and/or white, because of the way these color spaces stretch the RGB cube into cylinders.

For more control on how the colors are generated, you can specify constraints for each individual channel in the color space. Constraints can be either a constant number or an interval from where to pick the channel value:

import { random } from 'culori';

random('hsv', {
	h: 120, // number
	s: [0.25, 0.75] // interval
// ⇒ { mode: 'hsv', h: 120, s: 0.51…, v: 0.89… }

The alpha channel is excluded by default. To obtain colors with random alpha values, include a constraint for alpha:

import { random } from 'culori';

// ⇒ { mode: 'lrgb', r: 0.74…, g: 0.15…, b: 0.34… }

random('lrgb', { alpha: [0, 1] });
// ⇒ { mode: 'lrgb', r: 0.33…, g: 0.72…, b: 0.04…, alpha: 0.12… }

Displayable random colors

The value for any channel in the color space for which there are no constraints will be picked from the entire range of that channel. However, some color spaces, such as CIELAB or CIELCH, don't have explicit ranges for certain channels; for these, some approximate ranges have been pre-computed as the limits of the displayable sRGB gamut.

Even with these ranges in place, a combination of channel values may not be displayable. Check if that's the case with displayable(), and pass the color through a clamp* function to obtain a displayable version.

WCAG utilities

A couple of utility functions based on the Web Content Acccessibility Guidelines 2.0 specification.

# wcagLuminance(color)


Computes the relative luminance of a color.

import { wcagLuminance } from 'culori';

// ⇒ 0.2126

# wcagContrast(colorA, colorB)


Computes the contrast ratio between two colors.

import { wcagContrast } from 'culori';

wcagContrast('red', 'black');
// ⇒ 5.252


Filters apply certain graphical effects to a color. Culori currently implements two sets of filter functions:

CSS Filter Effects

These correspond to the filter effects defined in the W3C Filter Effects Module Level 1 specification.

The amount parameter is usually in the [0, 1] interval, but may go above 1 for some filters. The filters were designed for RGB colors, so the mode parameter is expected to be rgb or lrgb.

The resulting color is returned in the color space of the original color.

# filterBrightness(amount = 1, mode = 'rgb')


The brightness() CSS filter. An amount of 1 leaves the color unchanged. Smaller values darken the color (with 0 being fully black), while larger values brighten it.

import { filterBrightness } from 'culori';

let brighten = filterBrightness(2, 'lrgb');
// ⇒ { mode: 'rgb', r: 1.32…, g: 0.68…, b: 0.61… }

# filterContrast(amount = 1, mode = 'rgb')


The contrast() filter. An amount of 1 leaves the color unchanged. Smaller values decrease the contrast (with 0 being fully gray), while larger values increase it.

# filterSepia(amount = 1, mode = 'rgb')


The sepia() filter. An amount of 0 leaves the color unchanged, and 1 applies the sepia effect fully.

# filterGrayscale(amount = 1, mode = 'rgb')


The grayscale() filter. An amount of 0 leaves the color unchanged, and 1 makes the color fully achromatic.

# filterSaturate(amount = 1, mode = 'rgb')


The saturate() filter. An amount of 1 leaves the color unchanged. Smaller values desaturate the color (with 0 being fully achromatic), while larger values saturate it.

# filterInvert(amount = 1, mode = 'rgb')


The invert() filter. An amount of 0 leaves the color unchanged, and 1 makes the color fully inverted.

# filterHueRotate(degrees = 0, mode = 'rgb')


The hue-rotate() filter.

import { samples, interpolate, filterSepia, formatHex } from 'culori';

	.map(interpolate(['red', 'green', 'blue']))

// ⇒ ["#751800", "#664200", "#576c00", "#1a3e82", "#0010ff"];

Some of the effects may be obtained more straightforwardly with simple calculations in other color spaces. For example, hue rotation can just as well be implemented as color.h += angle in a cylindrical color space such as HSL.

Color vision deficiency (CVD) simulation

Simulate how a color may be perceived by people with color vision deficiencies (CVD).

# filterDeficiencyProt(severity = 1) → function (color)


Simulate protanomaly and protanopia. The severity parameter is in the interval [0, 1], where 0 corresponds to normal vision and 1 (the default value) corresponds to protanopia.

# filterDeficiencyDeuter(severity = 1) → function (color)


Simulate deuteranomaly and deuteranopia. The severity parameter is in the interval [0, 1], where 0 corresponds to normal vision and 1 (the default value) corresponds to deuteranopia.

# filterDeficiencyTrit(severity = 1) → function (color)


Simuate tritanomaly and tritanopia. The severity parameter is in the interval [0, 1], where 0 corresponds to normal vision and 1 (the default value) corresponds to tritanopia.


import { interpolate, filterDeficiencyProt, formatHex } from 'culori';
	.map(interpolate(['red', 'green', 'blue']))

// ⇒ ["#751800", "#664200", "#576c00", "#1a3e82", "#0010ff"];

Based on the work of Machado, Oliveira and Fernandes (2009), using precomputed matrices provided by the authors. References thanks to the colorspace package for R.

G. M. Machado, M. M. Oliveira and L. A. F. Fernandes, "A Physiologically-based Model for Simulation of Color Vision Deficiency," in IEEE Transactions on Visualization and Computer Graphics, vol. 15, no. 6, pp. 1291-1298, Nov.-Dec. 2009, doi: 10.1109/TVCG.2009.113.


# colorsNamed


An object whose keys are all the CSS named colors.

# round(n = 8)


Returns a rounder: a function with which to round numbers to at most n digits of precision.

import { round } from 'culori';

let approx = round(4);
// ⇒ 0.3839

# Available color spaces

The default import (culori) comes with all the color spaces pre-registered into the library. For convenience, you can also import directly mode as a shortcut to converter(mode). For example, instead of converter('hsl'), you can import hsl:

// Instead of this:
import { converter } from 'culori';
const hsl = converter('hsl');

// You can do this:
import { hsl } from 'culori';

On the other hand, when importing the tree-shaken version (culori/fn), color spaces need to be registered manually with useMode() based on their definition object:

import { useMode, modeHsl } from 'culori/fn';
const hsl = useMode(modeHsl);

The table below summarizes two pieces of information:

  • What the mode is for a specific color space built into Culori, which is also the name of the shortcut to the converter for that color space.
  • What the definition object for a color space is called.

The available color spaces are discussed into more detail on the Color Spaces page.

Mode Color space Definition object
a98 A98 RGB color space, compatible with Adobe RGB (1998) modeA98
cubehelix Cubehelix color space modeCubehelix
dlab DIN99o Lab color space modeDlab
dlch DIN99o LCh color space modeDlch
hsi HSI color space modeHsi
hsl HSL color space modeHsl
hsv HSV color space modeHsv
hwb HWB color space modeHwb
itp ICtCp color space modeItp
jab Jzazbz color space modeJab
jch Jzazbz in cylindrical form modeJch
lab CIELAB color space (D50 Illuminant) modeLab
lab65 CIELAB color space (D65 Illuminant) modeLab65
lch CIELCh color space (D50 Illuminant) modeLch
lch65 CIELCh color space (D65 Illuminant) modeLch65
lchuv CIELCHuv color space (D50 Illuminant) modeLchuv
lrgb Linear-light sRGB color space modeLrgb
luv CIELUV color space (D50 Illuminant) modeLuv
oklab Oklab color space modeOklab
oklch Oklab color space, cylindrical form modeOklch
p3 Display P3 color space modeP3
prophoto ProPhoto RGB color space modeProphoto
rec2020 Rec. 2020 RGB color space modeRec2020
rgb sRGB color space modeRgb
xyb XYB color space modeXyb
xyz50 XYZ with D50 white-point modeXyz50
xyz65 XYZ with D65 white-point modeXyz65
yiq YIQ color space modeYiq

Extending culori

# useMode(definition) → function.


Defines a new color space based on its definition. See Color mode definition for the expected object shape.

Returns a converter function for the newly defined mode.

import { useMode } from 'culori';

const hsl = useMode({
	mode: 'hsl'
	// ...

hsl('hsl(50 100% 100% / 100)');

# getMode(mode)


Returns the definition object for the mode color space.

# Color mode definition

The properties a definition needs are the following:

mode (string)

The string identifier for the color space.

channels (array)

A list of channels for the color space.

toMode (object)

A set of functions to convert from the color space we're defining to other color spaces. At least rgb needs to be included; in case a specific conversion pair between two color spaces is missing, RGB is used as the "buffer" for the conversion.

fromMode (object)

The opposite of toMode. A set of function to convert from various color spaces to the color space we're defining. At least rgb needs to be included.

ranges (object, optional)

The reference ranges for values in specific channels; if left unspecified, defaults to [0, 1].

parse (array, optional)

Any parsers for the color space that can transform strings into colors. These can be either functions, or strings — the latter is used as the color space's identifier to parse the color(<ident>) CSS syntax.

serialize (function or string, optional)

Defines how to serialize the color space to a CSS string with formatCss().

If you pass in a function, it receives a color object as its only argument, and should return a string that can be used in CSS. If you pass in a string, it's used as a color profile identifier, and the color is serialized using the color() CSS syntax. When omitted altogether, the default color profile identifier is --${mode}.


The default interpolations for the color space, one for each channel. Each interpolation is defined by its interpolator (the use key) and its fixup function (the fixup key). When defined as a function, a channel interpolation is meant to define its interpolator, with the fixup being a no-op.


The default Euclidean distance method for each channel in the color space; mostly used for the h channel in cylindrical color spaces.


The default average function for each channel in the color space; when left unspecified, defaults to averageNumber.

All built-in color spaces follow these conventions in regards to the channels array follows:

  • there are four channels in the color space;
  • the fourth channel is always alpha.

This makes sure differenceEuclidean() works as expected, but there may be more hidden assumptions in the codebase.

Here's a sample definition for the HSL color space:

  mode: 'hsl',
  fromMode: {
    rgb: convertRgbToHsl
  toMode: {
    rgb: convertHslToRgb
  channels: ['h', 's', 'l', 'alpha'],
  ranges: {
    h: [0, 360]
  parse: [parseHsl],
  serialize: serializeHsl,
  interpolate: {
    h: {
      use: interpolatorLinear,
      fixup: fixupHueShorter
    s: interpolatorLinear,
    l: interpolatorLinear,
    alpha: {
      use: interpolatorLinear,
      fixup: fixupAlpha
  difference: {
    h: differenceHueSaturation
  average: {
    h: averageAngle

# useParser(parser, mode)


Register a new parser. The parser can be:

  • a function, in which case the mode argument is not necessary.
  • a string representing the identifier to match in the color() syntax, in which case the mode argument is required.
import { useParser } from 'culori';

// Register custom parser
useParser(function(str) => {
  let color = {};
  // parse the string
  return color;

// Register `color(--oklab)` syntax
useParser('--oklab', 'oklab');

# removeParser(parser)


Remove a previously registered parser function or string, including parsers registered by default.

import { parse, parseNamed, removeParser } from 'culori';

// ⇒ Object { mode: "rgb", r: 1, g: 0.38823529411764707, b: 0.2784313725490196 }

// ⇒ undefined

# Low-level API

# Parsing functions

# parseHex(string) → color

Parses hex strings of 3, 4, 6, or 8 characters, with or without the # prefix, and returns rgb color objects.

import { parseHex } from 'culori';


# parseHsl(string) → color

Parses hsl(…) strings in the modern format and returns hsl color objects.

# parseHslLegacy(string) → color

Parses hsl(…) / hsla(…) strings in the legacy (comma-separated) format and returns hsl color objects.

# parseHwb(string) → color

Parses hwb(…) strings and returns hwb color objects.

# parseLab(string) → color

Parses lab(…) strings and returns lab color objects.

# parseLch(string) → color

Parses lch(…) strings and returns lch color objects.

# parseNamed(string) → color

Parses named CSS colors (eg. tomato) and returns rgb color objects.

# parseOklab(string) → color

Parses oklab(…) strings and returns oklab color objects.

# parseOklch(string) → color

Parses oklch(…) strings and returns oklch color objects.

# parseRgb(color) → color

Parses rgb(…) strings in the modern syntax and returns rgb color objects.

# parseRgbLegacy(color) → color

Parses rgb(…) / rgba(…) strings in the legacy (comma-separated) syntax and returns rgb color objects.

#parseTransparent(string) → color

Parses the transparent string and returns a transparent black rgb color object.

Serialization functions



Serialize a rgb color to a 6-character hex code. See formatHex() for details.



Serialize a rgb color to a 8-character hex code. See formatHex8() for details.



Serialize a hsl color to a hsl(…) string. See formatHsl() for details.



Serialize a rgb color to a rgb(…) string. See formatRgb() for details.

Conversion functions

Function Conversion
convertA98ToXyz65(color) → color a98xyz65
convertCubehelixToRgb(color) → color cubehelixrgb
convertDlchToLab65(color) → color dlchlab65
convertHsiToRgb(color) → color hsirgb
convertHslToRgb(color) → color hslrgb
convertHsvToRgb(color) → color hsvrgb
convertHwbToRgb(color) → color hwbrgb
convertJabToJch(color) → color jabjch
convertJabToRgb(color) → color jabrgb
convertJabToXyz65(color) → color jabxyz65
convertJchToJab(color) → color jchjab
convertLab65ToDlch(color) → color lab65dlch
convertLab65ToRgb(color) → color lab65rgb
convertLab65ToXyz65(color) → color lab65xyz65
convertLabToLch(color) → color lablch
convertLabToRgb(color) → color labrgb
convertLabToXyz50(color) → color labxyz50
convertLchToLab(color) → color lchlab
convertLchuvToLuv(color) → color lchuvluv
convertLrgbToOklab(color) → color lrgboklab
convertLrgbToRgb(color) → color lrgbrgb
convertLuvToLchuv(color) → color luvlchuv
convertLuvToXyz50(color) → color luvxyz50
convertOkhslToOklab(color) → color okhsloklab
convertOkhsvToOklab(color) → color okhsvoklab
convertOklabToLrgb(color) → color oklablrgb
convertOklabToOkhsl(color) → color oklabokhsl
convertOklabToOkhsv(color) → color oklabokhsv
convertOklabToRgb(color) → color oklabrgb
convertP3ToXyz65(color) → color p3xyz65
convertProphotoToXyz50(color) → color prophotoxyz50
convertRec2020ToXyz65(color) → color rec2020xyz65
convertRgbToCubehelix(color) → color rgbcubehelix
convertRgbToHsi(color) → color rgbhsi
convertRgbToHsl(color) → color rgbhsl
convertRgbToHsv(color) → color rgbhsv
convertRgbToHwb(color) → color rgbhwb
convertRgbToJab(color) → color rgbjab
convertRgbToLab65(color) → color rgblab65
convertRgbToLab(color) → color rgblab
convertRgbToLrgb(color) → color rgblrgb
convertRgbToOklab(color) → color rgboklab
convertRgbToXyb(color) → color rgbxyb
convertRgbToXyz50(color) → color rgbxyz50
convertRgbToXyz65(color) → color rgbxyz65
convertRgbToYiq(color) → color rgbyiq
convertXybToRgb(color) → color xybrgb
convertXyz50ToLab(color) → color xyz50lab
convertXyz50ToLuv(color) → color xyz50luv
convertXyz50ToProphoto(color) → color xyz50prophoto
convertXyz50ToRgb(color) → color xyz50rgb
convertXyz50ToXyz65(color) → color xyz50xyz65
convertXyz65ToA98(color) → color xyz65a98
convertXyz65ToJab(color) → color xyz65jab
convertXyz65ToLab65(color) → color xyz65lab65
convertXyz65ToP3(color) → color xyz65p3
convertXyz65ToRec2020(color) → color xyz65rec2020
convertXyz65ToRgb(color) → color xyz65rgb
convertXyz65ToXyz50(color) → color xyz65xyz50
convertYiqToRgb(color) → color yiqrgb