Web, print, photography, film, broadcast, art, science, and history. Canonical conversions, channels, gamut shapes, provenance, cited references, known limits. Convert any space to any other: rgb to oklch, slog3 to rec709, cct-duv to rgb.
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 162 × 161 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 colour management), a CLUT lattice for everything else (lcms, ArgyllCMS, ImageMagick).
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] – the same color, written three ways. A palette is a handful of chosen colors; a space is a way to write down any color at all.
It depends on the task:
The purpose grouping in the filter shelves the catalog the same way.
OKLCH. It keeps the same three controls – hue, chroma, lightness – but its lightness is trustworthy: in HSL, yellow at L=50 is about four times as bright as blue at L=50, so equal L doesn't mean equal brightness. In OKLCH it does. CSS supports oklch() natively.
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. Every dossier's interpolate row draws the same blend through two spaces side by side, so you can see the difference before choosing.
A color one space can describe but another – or your screen – can't show. A vivid Display P3 green simply 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.
Because each one fixed a real limitation of an earlier one, and the older ones are still in use. XYZ made color measurable (1931), Lab made differences comparable (1976), OKLab made screen steps look even (2020) – and along the way, broadcast, print and camera makers each needed encodings of their own. Grouping the catalog by era (in the filter) walks through the whole story.
Many are retired from daily use, but they still do quiet, real work. Archives still decode NTSC tapes; Cineon still opens the film scans of the '90s; PhotoYCC still holds family albums burned to PhotoCD. Reading old pictures takes the original math, and keeping that math available is part of what a reference is for. The history filter gathers them by era.
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). The rule of thumb: if the math was ever published, it belongs in the collection.
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.
Natively rgb(), hsl(), hwb(), lab(), lch(), oklab(), oklch(), plus color() for Display P3, Adobe RGB, ProPhoto, Rec. 2020, linear sRGB and XYZ. Each dossier's css tab writes the current color in copyable form; spaces CSS lacks ship as ICC profiles or LUTs instead.
A log curve packs a sensor's wide dynamic range into a small recordable code range, so highlights and shadows both survive. Every vendor ships its own: ARRI LogC4, Sony S-Log3, RED Log3G10. Footage is graded in log or through ACES, then delivered in Rec. 709 or Rec. 2100. Each log dossier exports the conversion as a .cube LUT your editor loads.
Its transfer function budgets code values for luminance far beyond a standard display's white. PQ addresses absolute levels up to 10,000 nits; HLG stays compatible with SDR broadcast; Rec. 2100 carries both. ICtCp and Jzazbz are their perceptual companions for grading and difference work.
Same colors, different conventions. This library keeps each space's conventional ranges – RGB 0–255, Lab lightness 0–100, hue 0–360 – where most libraries flatten everything to 0–1. Every card shows its ranges; in code they're space.oklch.range.
You don't have to take them on faith – everything here is set up to be checked:
formula-verification.md records what was checked against what – including a few places where the published papers themselves turned out to have errors.
Screens add light: red and green light sum to yellow, which is how every RGB space works. Paint subtracts it: each pigment absorbs part of the spectrum, and mixing multiplies the losses, so the result darkens toward brown. Each arithmetic gets its own shelf here – additive RGB on one, subtractive CMYK and the painters' RYB on the other.
They're close cousins, not quite color spaces. A colormap like viridis is a one-dimensional path drawn through a color space, made for painting data – so it lives one level above a conversion library. The connection is real, though: viridis was designed inside CAM02-UCS, chosen so its steps look even, and that space is here. To design your own, a straight line through OKLab is a good start.