UrColor — Universal Color Picker Library

Headless, accessible color picker primitives across 15 color spaces and four frameworks, with a benchmarked zero-dependency color engine and GPU-rendered gradients.
UrStackTypeScriptVueReactSvelteAngularWebGLAccessibility
2026

Part of UrStack, my open-source organization for TypeScript libraries. Siblings: UrVis, UrCharts, UrDock.

Overview

UrColor is a headless component library for building color interfaces. It ships the behaviour, meaning pointer math, keyboard handling, color conversion and ARIA wiring, and leaves every pixel of the presentation to the consumer.

Eight component families cover the vocabulary of color UI: area, slider, field, swatch, swatch picker, wheel, triangle, ring. Each one ships in all four bindings (Vue, React, Svelte, Angular) and works across 15 color spaces, from sRGB and HSL through OKLCH, Lab, Display P3 and CIE XYZ, on a zero-dependency CSS Color 4 engine.


Why I built it

Every color picker I reached for made the same trade. Either it looked exactly the way its authors wanted and fought every attempt to restyle it, or it was perceptually naive, interpolating gradients in sRGB and producing muddy ramps in modern color spaces.

The gap is structural, not accidental. React Aria and Reka UI both declare the identical public type, ColorSpace = 'rgb' | 'hsl' | 'hsb', and Ark UI and Zag.js share those same three. Every other picker on npm stores HSV or RGB and converts on the way out. An OKLCH area, a Lab slider or a P3 gamut boundary cannot be expressed by any of them, which is also why none of them need a GPU renderer.

UrColor separates the two concerns. The library owns correctness and accessibility, the design system owns appearance.


Key features

  • Eight component families: area, slider, field, swatch, swatch picker, wheel, triangle, ring
  • 15 color spaces: sRGB, linear sRGB, HSL, HSV, HWB, Lab, LCh, OKLab, OKLCH, Display P3, A98 RGB, ProPhoto RGB, Rec. 2020, XYZ D65, XYZ D50
  • Vue 3, React 18, Svelte 5 and Angular 21 bindings over a shared core, so the behaviour is the same in all four; Solid is planned
  • WAI-ARIA compliance, with full keyboard control and screen reader support, modelled on Adobe's React Spectrum
  • WebGL shaders that draw perceptually accurate Lab and OKLCH surfaces CSS gradients cannot express
  • CPU grid samplers (sampleBilinearGrid, samplePolarGrid and friends) that return Uint8ClampedArray RGBA ready for putImageData, which no other color library ships
  • Color science: ΔE CIEDE2000 and ΔE OK, WCAG 2.1 and APCA contrast, CSS Color 4 gamut mapping, hue-arc control
  • Zero runtime dependencies, since the CSS Color 4 engine is written from scratch
  • Optional i18n, in a separate package that names colors in up to 298 languages and translates channel labels into 77

Benchmarks

@urcolor/core is benchmarked against culori, colorjs.io, chroma-js, colord, tinycolor2 and @ctrl/tinycolor on every operation a picker performs: 59 groups, Apple M1, Bun 1.3, mitata. It leads most of them.

OperationUrColorNext fastestMargin
Parse a hex color137 nscolord, 138 ns1.0×
Reject an invalid color48 nscolord, 92 ns1.9×
sRGB to OKLCH145 nsculori, 153 ns1.1×
Mix two colors in OKLab283 nsculori, 669 ns2.4×
Read one channel8 nscolord, 22 ns2.8×
Serialize to hex44 nsculori, 66 ns1.5×
64-stop OKLab ramp2.61 µsculori, 9.88 µs3.8×
128×128 bilinear plane to RGBA4.4 msculori, 35.3 ms8.0×

Interpolation is where the lead is widest. The 64-stop OKLab ramp is 102× faster than chroma-js, and the bilinear plane follows, since its cost is mixing rather than conversion.

It does not win everything, and the docs publish where it loses. culori converts out of OKLCH faster (101 ns vs 165 ns) and gamut-maps faster (116 ns vs 209 ns), because UrColor routes every conversion through an XYZ D65 hub: 15 bridges instead of 210 direct paths. That same overhead, compounded across 16 384 pixels, is why culori edges the per-pixel channel samplers, and why the components render through WebGL, where one draw call covers the surface at any size.


Tech stack

  • Core: TypeScript, custom CSS Color 4 engine, WebGL
  • Bindings: Vue (on Reka UI), React, Svelte, Angular
  • Benchmarks: mitata on Bun
  • Docs: VitePress
Copyright © 2026