One JavaScript package. The same conversions run as WASM over whole images, in GLSL and WGSL shaders, and export as LUTs and ICC profiles.
import space from 'color-space' space.rgb.oklch(255, 128, 0) // → [0.732, 0.186, 53] space.oklch.rgb(0.65, 0.25, 180) // any of 205 × 204 pairs space.oklch.name // 'oklch' space.oklch.range // [[0,1], [0,0.4], [0,360]] // one space only – tree-shaken, 0.4-1.5 kB import oklch from 'color-space/oklch.js' oklch.rgb(0.65, 0.25, 180)
// values in the notations CSS already speaks – straight from the library color: oklch(0.732 0.186 53); // space.rgb.oklch(255, 128, 0) color: color(display-p3 0.936 0.529 0.199); // space.rgb.p3(255, 128, 0) color: color(xyz-d65 0.49 0.367 0.045); // space.rgb.xyz(…) / 100 – exact interchange // spaces CSS lacks ship as ICC profiles or LUTs – see those tabs; // every dossier's css tab writes the current color in copyable forms
// per-space metadata – sourced, ships as data.json
import data from 'color-space/data.json' with { type: 'json' }
const m = data.spaces.oklch
m.channels // [{symbol, min, max, name}, …]
m.refs // defining spec / paper links
m.year, m.by // 2020, 'Björn Ottosson'
// scalar or whole-buffer, zero-copy – a true WASM kernel
import space, { alloc } from 'color-space/wasm'
space.oklch.rgb(0.72, 0.16, 41) // → [246, 125, 79]
const buf = alloc(n) // n×3 Float64Array
space.rgb.oklch(buf) // in place – 27 spaces
// every pair as a GLSL shader chunk
import { glsl } from 'color-space/gl'
import oklch from 'color-space/gl/oklch'
const src = glsl(oklch, 'rgb')
// → self-contained: vec3 oklch_rgb(vec3 c)
// paste into any fragment shader, no uniforms
Any pair as a .cube LUT – load it into Resolve, Premiere, Final Cut, OBS or ffmpeg. Camera logs are verified against the official ACES vendor transforms.
Any space as an .icc v2 profile – matrix + tone curve for RGB working spaces (Photoshop, OS color management), a CLUT lattice for everything else (lcms, ArgyllCMS, ImageMagick).
// an MCP server – convert · gamut · css · space · spaces · cube –
// so an agent calls verified conversions instead of guessing them
npx -y color-space mcp
// Claude Code
claude mcp add color-space -- npx -y color-space mcp
// VS Code
code --add-mcp '{"name":"color-space","command":"npx","args":["-y","color-space","mcp"]}'
// Claude Desktop · Cursor · .mcp.json
{ "mcpServers": { "color-space": { "command": "npx", "args": ["-y", "color-space", "mcp"] } } }
// a Figma plugin: the selection's fills and strokes in any of the 205 spaces – // a plane and a bar per channel, cut to the file's gamut; copy the color as CSS figma.com/community/plugin/1691970922817908383/color-space git clone https://github.com/colorjs/color-space // Figma desktop: right-click › Plugins › Development › Import plugin from manifest… color-space/plugins/figma/manifest.json // what it reads, writes and keeps – plugins/figma
A coordinate system for color: axes, ranges, and a rule tying the numbers to measurable light. The same orange is rgb(255 128 0), oklch(0.732 0.186 53) and XYZ [49, 36.7, 4.5]. A palette is a handful of chosen colors; a space is a way to write down any color at all.
A color model is a shape for the numbers: RGB says three primaries, HSL says hue, saturation and lightness, CMYK says four inks. A color space ties a model to measurable light – which primaries, which white, which curve – so each set of numbers names exactly one color. rgb(255 0 0) is a different red in sRGB, Display P3 and Adobe RGB: one model, three spaces.
Everyday usage calls both spaces – so does CSS's color-mix(in hsl, …), and so does this catalog. A model without an anchor of its own borrows one: HSL and HSV here reshape sRGB, as in CSS.
It depends on the task:
To see every space sorted by task, group the catalog by purpose.
OKLCH. It keeps the same three controls – hue, chroma, lightness – but its lightness is trustworthy: in HSL, yellow at L=50 emits about 13 times the light of blue at L=50, so equal L doesn't mean equal brightness. In OKLCH it does. CSS supports oklch() natively.
Type or paste it into the color field at the top, say #ff8000. Every space on the page shows that color at once; in OKLCH it reads 0.732, 0.186, 53, which CSS writes as oklch(0.732 0.186 53). Each dossier's css tab gives the copyable form, and in code it's space.rgb.oklch(255, 128, 0).
Because they're computed in gamma-encoded sRGB, where averaging two colors' numbers produces darker, duller light than actually mixing their light would. Interpolating in OKLab avoids this; in CSS that's linear-gradient(in oklab, red, blue). Every dossier's interpolate row draws the same blend through two spaces side by side, so you can see the difference before choosing.
Open your camera's log space, such as S-Log3, LogC4 or Apple Log. In its conversion LUT block, choose Rec. 709 and your editor: DaVinci Resolve, Premiere Pro, Final Cut Pro, OBS, ffmpeg and others. Your browser writes the .cube file. It is a technical conversion with no tone mapping: highlights above diffuse white clip, and the look is yours to add.
A color one space can describe but another – or your screen – can't show. A vivid Display P3 green doesn't exist in sRGB: convert it, and the numbers fall outside the valid range. The atlas keeps this visible: the pill next to the current color names the tightest standard gamut that holds it, and out-of-range stretches ghost over a checkerboard.
A screen emits light; paper reflects what its inks don't absorb. Ink on paper reaches fewer vivid colors – saturated RGB blues, greens and reds fall outside it – so they print duller. Prepress converts through an ICC profile made for the press and paper, such as FOGRA39 for European coated stock or GRACoL in the US. This catalog's CMYK is the plain formula CSS uses for device-cmyk() without a profile – right for the numbers, not a proof of the print.
For the web and for sharing, sRGB: browsers and phones assume it when a file carries no profile. Display P3 keeps more of a vivid scene on Apple and many Android screens, as long as the profile stays embedded. Send Adobe RGB when a print lab asks for it. Keep ProPhoto RGB for 16-bit editing: its gamut is so wide that 8-bit files band.
Conversions are closed formulas, not lookup approximations, and round trips return within floating-point noise. The few exceptions are documented where they occur: OSA-UCS has no closed inverse, atlas systems like Munsell interpolate between published grades, and rgb output clamps to the sRGB gamut – route through XYZ or linear RGB when you need values outside it.
They are also checked: every space links the paper or spec it implements, values are tested against colorjs.io wherever the two overlap, and the camera logs against the Academy's official ACES transforms. formula-verification.md records what was checked against what – including a few places where the published papers themselves had errors.
If the numbers differ from another library's, compare ranges: this one keeps each space's conventional ranges – RGB 0–255, Lab lightness 0–100, hue 0–360 – where many flatten everything to 0–1. Every card shows its ranges; in code they're space.oklch.range.
Natively rgb(), hsl(), hwb(), lab(), lch(), oklab(), oklch(), plus color() for Display P3, Adobe RGB, ProPhoto, linear sRGB and XYZ (D65 and D50). CSS's rec2020 is a pure 2.4 gamma, not the BT.2020 curve of this catalog's Rec. 2020 – the same numbers would name a different color. Each dossier's css tab writes the current color in copyable form; spaces CSS lacks ship as ICC profiles or LUTs instead.
Open View and pick a Vision: red-blind (protanopia), green-blind (deuteranopia) or blue-blind (tritanopia). The catalog redraws as that eye sees it, so you can check that the colors of a palette stay apart. About 1 in 12 men have a color vision deficiency, by the NEI's count.
Those are licensed products: their colors are defined by data the owners sell, not by published formulas – so there's no open math to implement, and redistributing their tables wouldn't be legal. Close relatives whose math is public are here: Munsell (its 1943 renotation is public record) and RAL Design (defined directly in CIELAB). Any system whose math was ever published belongs here; suggest one that's missing.
@software{Ivanov_color-space_2026,
author = {Ivanov, Dmitry},
license = {CC0-1.0},
month = oct,
title = {{color-space}},
url = {https://github.com/colorjs/color-space},
version = {3.3.0},
year = {2026}
}Ivanov, D. (2026). color-space (Version 3.3.0) [Computer software]. https://github.com/colorjs/color-space
FilmLight’s camera-agnostic working gamut under T-Log in Baselight.
Scene-linear working gamut beneath T-Log; Baselight's camera-agnostic grading space.
componentscene-referredHDRD65
// one space, tree-shaken (0.4-1.5 kB) — its own edge: import egamut from 'color-space/egamut.js' egamut.xyz(0.22, 0.22, 0.22) // → [20.5166, 21.5861, 23.5085] // any-to-any, both ways — the wired hub: import space from 'color-space' space.egamut.rgb(0.22, 0.22, 0.22) // → [128, 128, 128] space.rgb.egamut(128, 128, 128) // → [0.22, 0.22, 0.22] // route: egamut → xyz → rgb