Library
color-map has zero runtime dependencies and "sideEffects": false, so a bundler keeps only what you import. Import everything from color-map:
import { parse, toCss, semanticPalette, toTailwindTheme } from 'color-map'The root re-exports the four subpaths below. With a bundler, importing from the root costs the same bytes as importing from a subpath. Without a bundler, for example in Node or in a <script type="module">, the root loads every module: about 26 ms instead of 3 ms as an ES module. Code that runs that way can import the subpath that holds what it needs instead.
| Entry point | Holds |
|---|---|
color-map |
Everything below. The original API is still here too, deprecated: import it from color-map/legacy. |
color-map/color |
The color core: parse, convert, serialize, gamut mapping, mix, resolve, contrast. |
color-map/scale |
createScale, createContrastScale, scaleFromBrand, tonalPalette, createGradient. |
color-map/palette |
harmony, harmonize, inverse, darkVariant, semanticPalette, validate. |
color-map/tokens |
The output formats: toDtcg, toCssVariables, toTailwindTheme. |
color-map/legacy |
The original API: createColors, createColorsFromMap, rgbHex, rgbaString. Unchanged. |
A color is a plain object, { colorSpace, components, alpha? }, with CSS names for the spaces and null for none. It is JSON, so it survives JSON.stringify.
import { parse, toCss, convert, mix, contrast, onColor } from 'color-map'
const blue = parse('#3b82f6')toCss(convert(blue, 'oklch')) // oklch(0.62308 0.18801 259.81453)toCss(mix(blue, 'white', 0.2, { in: 'oklab' }))contrast('#3b82f6', 'white') // 3.68, the WCAG 2 ratioonColor(blue) // a text color that reaches 4.5 on itFrom one brand color to a palette and a token file:
import { semanticPalette, validate, toTailwindTheme } from 'color-map'
const palette = semanticPalette({ brand: '#3b82f6', mode: 'light' })validate(palette).ok // true: every pair meets its targetconst css = toTailwindTheme(palette) // an @theme { ... } blockcolor-map/color
Section titled “color-map/color”- Spaces:
srgb,srgbLinear,hsl,hwb,lab,lch,oklab,oklch,displayP3,displayP3Linear,a98Rgb,prophotoRgb,rec2020,xyzD50,xyzD65. - Read and write:
parse,parseExpr,toCss,toHex,fromArgb,toArgb. - Convert:
convert,convertComponents,createConverter,spaces. - Gamut:
inGamut,toGamut(the CSS Color 4 algorithm, or clip). - Compare:
equals,deltaEOk. - Color 5:
mixfollows CSS Color 5 §13 andresolveevaluatescolor-mix(), relative colors with acalc()subset,light-dark()andcontrast-color(). - Contrast:
contrast,meets,onColor,findContrast, and theContrastFntype for supplying another measure. The default is WCAG 2. - Narrow forms:
createToHex,createInGamut,createToGamut,createDeltaEOk,createEquals,createMix,createContrast,createMeets,createOnColor,createFindContrast, each over only the spaces you give it. See below.
convert takes a space by name, so it bundles all 15 spaces. To parse and convert with only the spaces you use, pass them to createConverter. The converter throws for a space it was not given. See Size budgets for the difference; for example toHex costs about 3 kB, and createToHex(oklch) about 1.3 kB.
import { parse, toCss, createConverter, srgb, oklch } from 'color-map/color'
const convert = createConverter(srgb, oklch)toCss(convert(parse('#3b82f6'), 'oklch')) // oklch(0.62308 0.18801 259.81453)The helpers that convert inside also reach all 15 spaces: toHex, inGamut, toGamut, deltaEOk, equals, mix, contrast, meets, onColor and findContrast. Each has a narrow form, named create plus the helper’s name, that takes the spaces your colors are in and returns the helper over just those. The narrow helper takes the same arguments, except that it takes color objects, not CSS strings, so it does not carry parse. Run strings through parse first.
import { parse, createToGamut, createToHex, oklch } from 'color-map/color'
const toGamut = createToGamut() // srgb, oklch and oklab come with itconst toHex = createToHex(oklch) // srgb comes with ittoHex(toGamut(parse('oklch(0.7 0.3 150)'))) // #00c248Each factory adds the spaces the helper works in, so you list only the spaces of your colors, and of the gamut or the in space when it is not the default:
| Factory | Adds |
|---|---|
createToHex |
srgb |
createInGamut |
srgb |
createToGamut |
srgb, oklch, oklab |
createDeltaEOk |
oklab |
createEquals |
nothing |
createMix |
oklab |
createContrast, createMeets |
srgb, xyzD65 |
createOnColor, createFindContrast |
srgb, xyzD65, oklch, oklab |
A narrow helper throws for a space it was not given, and the error names that space. resolve has no narrow form: it reads CSS, which can name any space.
color-map/scale
Section titled “color-map/scale”createScale({ seed, steps })with the presetstailwind-11,radix-12,tone-13andeven, or a number of steps.createContrastScale({ seed, background, targets })returns one step per WCAG 2 target, each with its measured ratio and whether it was met.scaleFromBrand(brand, { mode })puts the brand color exactly at step 9, with a Radix-like shape.tonalPalette(hue, chroma, tones)holds an OKLCH hue and chroma across CIE L* tones, with Material’s 13 tones by default.createGradient(stops, count, { in, hue })returnscountcolors along a gradient through color stops. See Gradients.
color-map/palette
Section titled “color-map/palette”harmony(color, scheme)forcomplementary,split,analogous,triadicandtetradic, inoklch,lch,hslorhwb;harmonizenudges a color’s hue toward another;inverseflips lightness (or RGB).darkVariant(scale, strategy)withtone-swap,mirrororlightness-invert.semanticPalette({ brand, mode })returns scales and role pairs (primary,primaryContainer,success,warning,danger,info,surface,surfaceVariant,outline), each with its background, text color, measured ratio and target.validate(palette)returns{ ok, issues }: pairs below target, non-monotonic scales, colors outside sRGB.
color-map/tokens
Section titled “color-map/tokens”Each exporter takes a token tree: a color, a CSS color string, an array (a scale), an object of these nested to any depth, or a SemanticPalette as is. An array of 11 colors gets the keys 50 to 950; any other length gets 1 to N.
toDtcg(tree)returns a W3C design-token (2025.10) group; write it withJSON.stringify.toCssVariables(tree, { selector, prefix, space })returns one rule of custom properties.toTailwindTheme(tree, { namespace, inline, space })returns a Tailwind v4@themeblock. The default space isoklch. The export panel shows each output, and this site’s build compiles the Tailwind output with Tailwind v4.
color-map/legacy
Section titled “color-map/legacy”The original API works only in sRGB, interpolates in gamma-encoded sRGB, rounds to integers and takes alpha as a separate argument. It stays in color-map/legacy for all of 3.x. The root color-map still exports it in 2.x, marked deprecated; 3.0 removes it from the root, so change from 'color-map' to from 'color-map/legacy' for these names.
| Legacy | Replacement |
|---|---|
rgbHex(rgb) |
toHex(color) |
rgbaString(rgba) |
toCss(color) |
createColors(from, to, shades, alpha) |
createGradient([from, to], shades, { in: 'srgb' }). The results differ: createGradient does not round. |
createColorsFromMap(colormap, shades, alpha) |
createGradient(stops, shades, { in: 'srgb' }). See From createColorsFromMap. |
Gradients
Section titled “Gradients”createGradient samples a gradient through color stops, the way a CSS gradient blends them. Use it for data-visualization color maps, such as blue to lime to yellow for a heatmap:
import { createGradient } from 'color-map'
const heatmap = createGradient( [{ color: 'blue', at: 0 }, { color: 'lime', at: 0.6 }, { color: 'yellow', at: 1 }], 72, { in: 'oklab' })- A stop is
{ color, at? }, or a bare color. The color is a CSS color string or a color object. atruns from 0 at the start to 1 at the end. Stops withoutatspread evenly between their neighbors, as in CSS. A position before an earlier stop’s moves up to it, and two stops at one position switch color there.- The
countcolors are evenly spaced from 0 to 1, so the first and last fall on the ends. Before the first stop and after the last, the color holds. inandhuework as they do inmix(). The default space isoklab, and the colors come back in that space.- Alpha comes from each stop’s color, and is 1 when the color has none. Alpha changes linearly between stops, and the other channels are premultiplied by it, so a transparent stop does not tint its neighbor.
From createColorsFromMap
Section titled “From createColorsFromMap”createColorsFromMap is deprecated. Turn its color map into stops and interpolate in srgb:
import { createGradient, toCss } from 'color-map'
const summer = [{ index: 0, rgb: [0, 128, 102] }, { index: 1, rgb: [255, 255, 102] }]
const stops = summer.map(({ index, rgb: [r, g, b, a = 1] }) => ({ color: `rgb(${r} ${g} ${b} / ${a})`, at: index }))const colors = createGradient(stops, 60, { in: 'srgb' }).map((c) => toCss(c))The results come close to the old ones, but do not match them:
- Channels run from 0 to 1 and are not rounded to whole numbers. With two stops, each channel is within 0.5 of the old 0 to 255 value.
- The old function started a new run at each inner stop, so it repeated that stop’s color.
createGradientplaces every color on one even spacing. - The old
alphaargument set alpha for the whole map. Here each stop carries its own alpha in its color.
Contrast guarantees
Section titled “Contrast guarantees”Contrast follows luminance, and a tone is CIE L*. The tone-based scales (tone-13, tonalPalette) therefore let you pick a text pair by tone alone:
| Tone gap | WCAG 2 contrast |
|---|---|
| 40 or more | at least 3 |
| 50 or more | at least 4.48, not exactly 4.5 (4.48 at the worst pair, tones 100 and 50) |
| 100 | holds 4.5 |
Do not rely on a gap of 50 for the 4.5 threshold. Use a larger gap, or check the pair with contrast or meets. semanticPalette measures every pair and validate reports any that fall short.
Color spaces and browser support
Section titled “Color spaces and browser support”color() accepts display-p3-linear, which is in CSS Color 4. Browser support for it is unverified, so test it in the browsers you target before shipping it in a stylesheet. The library parses, converts and serializes it either way.
Size budgets
Section titled “Size budgets”Each budget is a gzip limit enforced by size-limit in CI. The measured size is smaller. The table is read from size-limit in packages/color-map/package.json, so it matches what CI enforces. For how these sizes compare with other libraries, see Compare.
| Import | What is measured | Budget (gzip) |
|---|---|---|
| existing API | { createColors, createColorsFromMap, rgbHex, rgbaString } | 0.45 kB |
| existing API from the root | { createColors, createColorsFromMap, rgbHex, rgbaString } | 0.45 kB |
| convert between two spaces | { createConverter, srgb, oklch } | 1.22 kB |
| parse and convert between two spaces | { parse, createConverter, srgb, oklch } | 3.95 kB |
| toHex over every space | { toHex } | 3.01 kB |
| toHex from oklch, narrow | { createToHex, oklch } | 1.31 kB |
| toGamut over every space | { toGamut } | 3.27 kB |
| toGamut, narrow | { createToGamut } | 1.61 kB |
| mix over every space | { mix } | 6.26 kB |
| mix, narrow | { createMix } | 1.52 kB |
| contrast helpers over every space | { contrast, meets, onColor, findContrast } | 6.77 kB |
| contrast helpers, narrow | { createContrast, createMeets, createOnColor, createFindContrast } | 2.39 kB |
| every converting helper over every space | { toHex, toGamut, inGamut, deltaEOk, equals, mix, contrast, meets, onColor, findContrast } | 7.63 kB |
| every converting helper, narrow | { createToHex, createToGamut, createInGamut, createDeltaEOk, createEquals, createMix, createContrast, createMeets, createOnColor, createFindContrast } | 3.29 kB |
| full color core | { parse, toCss, toHex, toGamut, inGamut, deltaEOk, equals, fromArgb, toArgb, convert, spaces } | 6.76 kB |
| core with mix | { parse, toCss, toGamut, mix } | 7.04 kB |
| core with mix and contrast | { parse, toCss, toGamut, mix, contrast, meets, onColor, findContrast } | 7.77 kB |
| scales | * | 9.1 kB |
| palette | * | 8.46 kB |
| tokens | * | 7.59 kB |
| everything | * | 10.71 kB |