| name | helmlab |
| description | Correct usage of the helmlab color library (npm + PyPI) — 1.0 namespaced API (hl.gen / hl.metric / hl.tokens), branded Lab types, wide-gamut generation, and the measurement metrics (difference/jnd/ciede2000/confidence). Use whenever code imports or should import helmlab. |
helmlab — correct API usage (1.0)
Perceptual color library, JS (npm i helmlab, zero deps) and Python
(pip install helmlab) with identical outputs — a permanent parity gate
verifies the full surface: every string output byte-identical, numeric worst
~1e-12, hex round-trips bit-exact on a 1728-color grid in both languages.
JS is camelCase, Python snake_case; same math, same namespaces.
The one mental model you need
One Helmlab instance, three namespaces. Color strings in, color
strings out — Lab is the advanced layer:
| Namespace | Purpose | Key methods |
|---|
hl.gen | CREATE colors (GenSpace) | gradient mix palette scale hueRing harmonies rotateHue vivid cusp maxChroma gamutMap ensureContrast adaptToMode adaptPair |
hl.metric | MEASURE colors (MetricSpace) | difference euclidean ciede2000 jnd distance confidence nearest info toCss inGamut toLch/fromLch |
hl.tokens | EXPORT design tokens | css android iosP3/ios_p3 swift cssVariables tailwind multiFormat json |
Each namespace has its own fromHex; their Lab types are branded
(GenLab / MetricLab) — cross-passing raises TypeError instead of
silently producing a wrong color (this replaces the entire 0.x
fromHex vs genFromHex footgun family).
Color-string params accept '#rrggbb', '#rgb',
'color(display-p3 r g b)', 'color(rec2020 r g b)' everywhere.
Verified quick reference (JS; Python is the snake_case mirror)
import { Helmlab } from 'helmlab';
const hl = new Helmlab();
hl.gen.gradient('#0000ff', '#ffffff', 16);
hl.gen.gradient('#00f', '#fff', 8, { gamut: 'display-p3' });
hl.gen.mix('#ff0000', '#0000ff');
hl.gen.scale('#3b82f6');
hl.gen.palette('#3b82f6', 10);
hl.gen.hueRing(12, { lightness: 0.6, chroma: 0.15 });
hl.gen.harmonies('#3b82f6', 'triadic');
hl.gen.vivid('#6488b8', { gamut: 'display-p3' });
hl.gen.cusp(263);
hl.gen.ensureContrast('#3b82f6', '#ffffff');
hl.gen.ensureContrast('#3b82f6', '#808080', 7, { strict: true });
hl.gen.adaptToMode('#3b82f6', 'light', 'dark');
const lch = hl.gen.toLch(hl.gen.fromHex('#3b82f6'));
hl.gen.rotateHue('#3b82f6', 120);
hl.metric.difference('#ff0000', '#00ff00');
hl.metric.euclidean('#000000', '#ffffff');
hl.metric.ciede2000('#ff0000', '#00ff00');
hl.metric.jnd('#808080', '#828282');
hl.metric.confidence('#808080', '#828282');
hl.gen.contrastRatio('#ffffff', '#3b82f6');
hl.metric.info('color(display-p3 1 0 0)');
hl.metric.toCss(hl.metric.fromHex('#ff0000'), 'display-p3');
hl.tokens.css('#3b82f6', 'oklch');
hl.tokens.tailwind(hl.gen.scale('#3b82f6'), 'primary');
hl.tokens.cssVariables(hl.gen.scale('#3b82f6'), '--primary');
Gotchas
- 0.x flat API is GONE (1.0 clean break):
hl.fromHex, hl.gradient,
hl.semanticScale, hl.deltaE, hl.genFromHex, hl.export(),
TokenExporter, hl.toHexP3 no longer exist. Mapping: root conversions
→ hl.metric.*; generation → hl.gen.* (semanticScale→scale,
paletteHues→hueRing(count, {...})); deltaE/euclideanDistance →
metric.euclidean; deltaE2000 → metric.ciede2000;
perceptualDistance/distanceFromLab → metric.distance;
differenceWithConfidence → metric.confidence; nearestColor →
metric.nearest; exporter → hl.tokens (hex-in).
- GenSpace L and C are 0–1-scale.
L - 15 gives black; darken with
e.g. L - 0.15.
difference() saturates near ~0.15 for very dissimilar pairs — rank
preserved, absolute values plateau. Unbounded: euclidean (0–1.6+);
threshold units: jnd.
palette() runs light→dark and the base is only approximate inside
it; scale() is the Tailwind-style API where level 500 is exact.
metric.distance(labA, labB) takes MetricLab in BOTH languages —
the 0.x Python-XYZ/JS-Lab asymmetry is gone. XYZ-in lives only on
Python's raw MetricSpace class.
- Cross-space Lab throws. If you see
TypeError: hl.metric got a GenLab, convert the original color with the
right namespace's fromHex — don't cast.
- Raw classes (advanced):
hl.gen.space / hl.metric.space expose
the live GenSpace/AnalyticalSpace; constructing raw JS spaces still needs
compiled params (new GenSpace(compileGenParams(getDefaultGenParams()))).
ensureContrast fallback: if the ratio is unreachable even with pure
black/white, default warns and returns best effort; strict: true (JS
opts / Python kwarg) raises ContrastError.
Ecosystem
- PostCSS:
npm i postcss-helmlab → helmlab(), helmlch(),
helmgen(), helmgenlch() CSS functions; since 1.0 the P3/Rec2020
output uses the TRUE gamut-mapped paths of both spaces.
- Color.js (master): spaces
helmgen, helmgenlch, helmlab-metric +
deltaE method "Helmlab".
- Python extras:
pip install 'helmlab[datasets]'; py.typed shipped.
When NOT to use helmlab
Near-achromatic gradient mastering and deutan-safe palettes → OKLab is
measurably better. CSS-only with no JS budget → native oklch(). HDR/PQ →
Jzazbz/ICtCp. Full routing table: the companion color-space-routing
skill. Docs: helmlab.space/docs · honesty tables: helmlab.space/benchmark