npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

helmlab

v1.0.0

Published

Perceptual color space for UI design — human-tuned Lab with WCAG contrast, gamut mapping, and palette generation

Readme

helmlab

Perceptual color library for UI design systems — two purpose-built Lab spaces in one zero-dependency package.

npm version bundle size Color.js license

Website · Docs · Playground · Benchmark · Paper

  • MetricSpace — measures how different two colors look. STRESS 22.48 on COMBVD with Bradford CAT (CIEDE2000: 29.20; cross-validated estimate ~24.3 — full overfit analysis).
  • GenSpace — creates colors: gradients, palettes, gamut mapping. 62–9 vs OKLab (19 ties) across 90 ColorBench metrics; 360/360 valid gamut cusps in sRGB and Display P3.

~17.8 KB gzipped · zero dependencies · ESM + CJS · full TypeScript types · tree-shakeable (sideEffects: false) · works in browsers and Node.js.

npm install helmlab

Quick start

One Helmlab instance, three namespaces: hl.gen creates colors, hl.metric measures them, hl.tokens exports design tokens.

import { Helmlab } from 'helmlab';

const hl = new Helmlab();

// Create — GenSpace
hl.gen.gradient('#0000ff', '#ffffff', 16);   // stays blue through the midpoint
hl.gen.scale('#3b82f6');                     // Tailwind-style { '50': ..., '950': ... } — 500 is your exact input
hl.gen.palette('#3b82f6', 10);               // lightness ramp, light → dark

// Measure — MetricSpace
hl.metric.difference('#ff0000', '#00ff00');  // 0.148 — the trained perceptual metric (STRESS 22.48)
hl.metric.jnd('#808080', '#828282');         // 0.33 — in just-noticeable-difference units
hl.metric.euclidean('#ff0000', '#00ff00');   // 1.62 — plain Euclidean Lab, unbounded

// Accessibility
hl.gen.contrastRatio('#ffffff', '#3b82f6');  // 3.68 (WCAG 2.1)
hl.gen.ensureContrast('#3b82f6', '#ffffff'); // darkens until 4.5:1, hue preserved

Two spaces, two Lab types

Each namespace has its own fromHex, and their Lab values are branded types (GenLab / MetricLab). Passing one space's Lab to the other throws a TypeError — the 0.x silent-wrong-color footgun is structurally gone. Everyday use never touches Lab: color strings in, color strings out.

| Namespace | Space | For | |---|---|---| | hl.gen | GenSpace | gradient mix palette scale hueRing harmonies rotateHue vivid cusp maxChroma gamutMap ensureContrast adaptToMode adaptPair | | hl.metric | MetricSpace | difference euclidean ciede2000 jnd distance confidence nearest info toCss | | hl.tokens | — | css android iosP3 swift cssVariables tailwind multiFormat json (all take color strings) |

Measuring color difference

// Recommended: the trained metric (Minkowski + compression, fit on COMBVD).
// Saturates near ~0.15 for very dissimilar pairs — order is preserved.
hl.metric.difference('#3b82f6', '#4c8af7');   // 0.0227

// In threshold units: <1 likely unnoticed, 1–2 subtle, >2 clearly visible
hl.metric.jnd('#3b82f6', '#4c8af7');

// Experimental: difference + how much real observers would disagree about it
hl.metric.confidence('#808080', '#828282');
// { de: 0.0117, pNoticeable: 0.077, reliability: 0.41, reliable: false, ... }

// Catalog matching (most perturbation-stable for argmax): CIEDE2000
hl.metric.nearest('#3b82f6', ['#3b7ff0', '#ff0000'], 'ciede2000');

Generating colors

// Perceptually even gradient: CIEDE2000 arc-length reparameterization —
// equal visual step sizes on any pair. Every generation function takes
// { gamut: 'srgb' | 'display-p3' | 'rec2020' }.
hl.gen.gradient('#ef4444', '#3b82f6', 16);
hl.gen.gradient('#0000ff', '#ffffff', 16, { gamut: 'display-p3' });

// The visual midpoint on the same path (not the coordinate average)
hl.gen.mix('#ef4444', '#3b82f6', 0.5);

// Hue ring at fixed lightness/chroma (categorical palettes)
hl.gen.hueRing(12, { lightness: 0.6, chroma: 0.15 });

// Harmonies: constant-L,C hue rotations (matched lightness & colorfulness)
hl.gen.harmonies('#3b82f6', 'triadic');      // also: complementary, analogous, tetradic, split_complementary
hl.gen.rotateHue('#3b82f6', 120);

// Cusp geometry — the 360/360/360 strength, exposed:
hl.gen.cusp(263);                            // [L, C] of the most colorful point of a hue
hl.gen.maxChroma(0.6, 263, 'display-p3');    // chroma headroom of a wide gamut
hl.gen.vivid('#6488b8', { gamut: 'display-p3' }); // same L & hue, chroma → boundary

Wide gamut & tokens

// Wide-gamut INPUT everywhere a color string is accepted:
hl.metric.info('color(display-p3 1 0 0)');   // { inSrgb: false, inP3: true, ... }

// Wide-gamut OUTPUT:
const lab = hl.metric.fromHex('#ff0000');
hl.metric.toCss(lab, 'display-p3');          // 'color(display-p3 0.9176 0.2003 0.1386)'
hl.metric.toCss(lab, 'rec2020');
hl.metric.inGamut(lab, 'display-p3');

// Tokens: color strings in, platform strings out (no Lab, no footgun)
hl.tokens.css('#3b82f6', 'oklch');           // 'oklch(62.3% 0.1881 259.8)'
hl.tokens.tailwind(hl.gen.scale('#3b82f6'), 'primary');
hl.tokens.cssVariables(hl.gen.scale('#3b82f6'), '--primary');
// also: css(c,'hex'|'rgb'|'hsl'|'p3'|'rec2020') / android / iosP3 / swift / multiFormat / json

Dark / light mode

hl.gen.adaptToMode('#3b82f6', 'light', 'dark');           // soft L-inversion, hue kept
hl.gen.adaptPair('#3366ff', '#ffffff', 'light', 'dark');  // [fg, bg] re-contrasted
hl.gen.meetsContrast('#1e40af', '#ffffff', 'AAA');        // WCAG check without modifying
hl.gen.ensureContrast('#3b82f6', '#808080', 7, { strict: true }); // throws ContrastError if unreachable

Advanced: raw spaces

import { GenSpace, MetricSpace, srgbToXyz,
         compileGenParams, getDefaultGenParams,
         compileParams, getDefaultParams } from 'helmlab';

const gen = new GenSpace(compileGenParams(getDefaultGenParams()));
const metric = new MetricSpace(compileParams(getDefaultParams()));
const lab = gen.fromXYZ(srgbToXyz([0.2, 0.5, 0.8]));   // raw Lab, no gamut mapping

Custom parameter sets (research / retraining) are accepted by both constructors — see the docs.

Python parity

The helmlab PyPI package is the same math with a snake_case API (hl.gen.ensureContrasthl.gen.ensure_contrast). A permanent parity gate (tests/parity-1.0.test.ts, reference generated by the Python package) covers the full public surface: every string output is byte-identical, numeric worst-case difference ~1e-12, hex round-trips bit-exact on a 1728-color grid in both languages. Conversion precision: XYZ round-trip 2.9e-15 (MetricSpace) / 5.8e-9 (GenSpace).

Using with Color.js

Helmlab is merged into color-js/color.js master: spaces helmgen, helmgenlch, helmlab-metric plus a "Helmlab" deltaE method. Not yet in the published colorjs.io release — until then use this package, or npm install github:color-js/color.js.

Honest limits

No color space wins everywhere. OKLab is still the better pick for near-achromatic gradient mastering, CVD-deutan-optimized palettes, native CSS oklch(), or a ~2 KB bundle. CIEDE2000 still edges MetricSpace on the small near-threshold tolerance datasets (LEEDS / RIT-DuPont). Full loss list with numbers: helmlab.space/benchmark.

License

MIT © Görkem Yıldız