npm.io
2.0.4 • Published 7 months ago

use-color

Licence
MIT
Version
2.0.4
Deps
0
Size
696 kB
Vulns
0
Weekly
0
Stars
280

hero

use-color

A TypeScript-first, OKLCH-based, framework-agnostic color library
that validates color strings at compile time.

Never ship an invalid color string again.

NPM version Bundle size TypeScript Coverage

Playground

Compile-time Type Checking

The only color library that catches invalid colors at compile time.

+ color('#00ffaa')
- color('#00ffzz')
// Argument of type '"#00ffzz"' is not assignable to parameter of type...

+ color('rgb(255, 128, 0)')
- color('rgb(255, 128,)')
// Argument of type '"rgb(255, 128,)"' is not assignable to parameter of type...

+ color('oklch(0.7 0.15 180)')
- color('oklch(invalid)')
// Argument of type '"oklch(invalid)"' is not assignable to parameter of type...

+ color('hsl(180, 50%, 50%)')
- color('hsl(180, 50%)')
// Argument of type '"hsl(180, 50%)"' is not assignable to parameter of type...

This is the moat. No other library—not colord, chroma-js, or tinycolor2—can do this.

Installation

pnpm add use-color
npm install use-color
yarn add use-color

Usage

Parse Colors
import { color } from "use-color";

// All formats supported
const red = color("#ff0000");
const green = color("rgb(0, 255, 0)");
const blue = color("hsl(240, 100%, 50%)");
const purple = color("oklch(0.5 0.2 300)");
const coral = color("coral"); // Named CSS colors

// Object input
const custom = color({ r: 255, g: 128, b: 0 });
Transform Colors
const c = color("#3b82f6");

// Lightness
c.lighten(0.2); // Perceptually uniform lightening
c.darken(0.1);

// Saturation
c.saturate(0.3);
c.desaturate(0.2);
c.grayscale();

// Hue
c.rotate(45); // Rotate hue by degrees
c.complement(); // Rotate 180°

// Alpha
c.alpha(0.5); // Set alpha
c.opacify(0.1); // Increase alpha
c.transparentize(0.2); // Decrease alpha

// Others
c.invert();
c.mix(otherColor, 0.5);
Chain Transformations
const result = color("#e11d48")
  .lighten(0.1)
  .saturate(0.2)
  .rotate(15)
  .alpha(0.9)
  .toHex();

// "#ff2d5c"
Output Formats
const c = color("#3b82f6");

// Hex
c.toHex(); // "#3b82f6"
c.toHex8(); // "#3b82f6ff"
c.toHexShort(); // null (not compressible) or "#38f"

// RGB
c.toRgb(); // { r: 59, g: 130, b: 246, a: 1 }
c.toRgbString(); // "rgb(59, 130, 246)"
c.toRgbaString(); // "rgba(59, 130, 246, 1)"
c.toRgbModern(); // "rgb(59 130 246)"  (CSS Level 4)

// HSL
c.toHsl(); // { h: 217, s: 0.91, l: 0.60, a: 1 }
c.toHslString(); // "hsl(217, 91%, 60%)"
c.toHslaString(); // "hsla(217, 91%, 60%, 1)"
c.toHslModern(); // "hsl(217 91% 60%)"

// OKLCH (perceptually uniform)
c.toOklch(); // { l: 0.62, c: 0.19, h: 255, a: 1 }
c.toOklchString(); // "oklch(0.62 0.19 255)"

// Display P3 (wide gamut)
c.toP3String(); // "color(display-p3 0.35 0.52 0.93)"

// CSS (smart default)
c.toCss(); // "#3b82f6"
c.toCss({ format: "oklch" }); // "oklch(0.62 0.19 255)"
Safe Parsing
import { tryColor } from "use-color";

const result = tryColor(userInput);

if (result.ok) {
  // result.value is a Color instance
  console.log(result.value.toHex());
} else {
  // result.error is a ColorParseError
  console.error(result.error.message);
  console.error(result.error.code); // 'INVALID_HEX' | 'INVALID_RGB' | ...
}
Color Properties
const c = color("#3b82f6");

c.getAlpha(); // 1
c.getLightness(); // 0.62 (OKLCH lightness)
c.getChroma(); // 0.19 (OKLCH chroma)
c.getHue(); // 255 (OKLCH hue)

c.isDark(); // false
c.isLight(); // true

Accessibility

Built-in WCAG 2.1 contrast checking and auto-adjustment.

import { contrast, isReadable, ensureContrast, luminance } from "use-color";

const text = "#374151";
const background = "#ffffff";

// Relative luminance (WCAG formula)
luminance(text); // 0.18
luminance(background); // 1.0

// Contrast ratio (1-21)
contrast(text, background); // 7.49

// Readability checks
isReadable(text, background); // true (default: AA 4.5:1)
isReadable(text, background, { level: "AAA" }); // true (7:1)
isReadable(text, background, { level: "AA", size: "large" }); // true (3:1)

// Auto-adjust for accessibility
const adjusted = ensureContrast("#888888", background, 4.5);
adjusted.toHex(); // "#767676" (meets 4.5:1 ratio)
APCA (Experimental)

APCA is the next-generation contrast algorithm for WCAG 3.0.

import { apcaContrast } from "use-color";

// Returns Lc value (-108 to +106)
apcaContrast("#000000", "#ffffff"); // 106 (maximum contrast)
apcaContrast("#767676", "#ffffff"); // 63 (good for body text)

Note: APCA is still in development and not yet a W3C standard. Use for experimental projects.

Why OKLCH?

use-color uses OKLCH internally for all color manipulations. This ensures perceptually uniform results:

Operation HSL (traditional) OKLCH (use-color)
Lighten yellow vs blue Yellow appears brighter Both appear equally lighter
Desaturate Colors shift unexpectedly Consistent desaturation
Mix colors Muddy intermediates Vibrant, natural gradients

OKLCH is the color space used by Tailwind CSS v4, shadcn/ui, and modern design systems.

Display P3 Wide Gamut

93% of browsers support Display P3. All Apple devices since 2016 have P3 displays.

import { color, isInP3Gamut, clampToP3Gamut } from "use-color";

const vibrant = color("oklch(0.7 0.35 150)");

// Check gamut
isInP3Gamut(vibrant); // true (P3 is ~25% larger than sRGB)

// Output for modern displays
vibrant.toP3String(); // "color(display-p3 0.12 0.87 0.45)"

// Fallback for older displays
vibrant.toHex(); // "#00d96f" (clamped to sRGB)

Tree-Shakeable

Import only what you need. Every function is individually exportable.

// Only bundles these specific functions
import { parseHex, toHex, lighten } from "use-color";

const rgba = parseHex("#ff0000");
const lighter = lighten(rgba, 0.2);
const hex = toHex(lighter);

Modular Imports

For smaller bundles, import only what you need:

// Core only (~8KB gzip) - parsing, formatting, manipulation
import { color, lighten, darken, toHex } from "use-color/core";

// Accessibility functions (~3KB gzip)
import { contrast, isReadable, apcaContrast } from "use-color/a11y";

// Named colors (~2KB gzip) - adds 'coral', 'rebeccapurple', etc.
import { parseNamed, NAMED_COLORS } from "use-color/names";

// Display P3 wide gamut (~3KB gzip)
import { toP3String, isInP3Gamut } from "use-color/p3";
Import Size (gzip) Description
use-color ~10KB Full bundle with everything
use-color/core ~8KB Core color operations
use-color/a11y ~3KB WCAG contrast, APCA
use-color/names ~2KB CSS named colors
use-color/p3 ~3KB Display P3 gamut

Type Guards & Assertions

import { isHex, isRgb, isColor, assertHex } from "use-color";

// Type guards (return boolean)
isHex("#ff0000"); // true
isHex("#gggggg"); // false
isRgb("rgb(1,2,3)"); // true
isColor(something); // type narrowing

// Assertions (throw on invalid)
assertHex(userInput); // throws ColorParseError if invalid
// After this line, TypeScript knows userInput is valid

Comparison

Feature use-color colord chroma-js tinycolor2
Compile-time validation
OKLCH native Plugin
P3 wide gamut
Accessibility Built-in Plugin Basic
Tree-shakeable
Modular imports
Bundle size (gzip) 8-10KB 2.1KB 16.7KB 5.3KB
TypeScript Excellent Good Poor Poor
Maintained 3yr

Migration

From colord
- import { colord } from 'colord';
+ import { color } from 'use-color';

- const c = colord('#ff0000');
+ const c = color('#ff0000');

// Methods are mostly identical
c.lighten(0.1).toHex();

Key differences:

  • use-color uses OKLCH for transformations (better visual results)
  • Compile-time validation catches errors earlier
From chroma-js
- import chroma from 'chroma-js';
+ import { color, mix } from 'use-color';

- const c = chroma('#ff0000');
+ const c = color('#ff0000');

- chroma.mix('#ff0000', '#0000ff', 0.5);
+ mix(color('#ff0000'), color('#0000ff'), 0.5);
From tinycolor2
- import tinycolor from 'tinycolor2';
+ import { color } from 'use-color';

- const c = tinycolor('#ff0000');
+ const c = color('#ff0000');

- c.lighten(10).toString();
+ c.lighten(0.1).toHex();  // Note: use-color uses 0-1 range

API Reference

Factory Functions
Function Description
color(input) Create Color instance (throws on invalid)
tryColor(input) Safe parsing, returns Result<Color, ColorParseError>
Color Methods
Manipulation (returns new Color)
Method Description
.lighten(amount) Increase lightness (0-1)
.darken(amount) Decrease lightness (0-1)
.saturate(amount) Increase chroma (0-1)
.desaturate(amount) Decrease chroma (0-1)
.grayscale() Remove all chroma
.rotate(degrees) Rotate hue
.complement() Rotate hue 180°
.alpha(value) Set alpha (0-1)
.opacify(amount) Increase alpha
.transparentize(amount) Decrease alpha
.invert() RGB inversion
.invertLightness() OKLCH lightness inversion
.mix(color, ratio) Mix with another color
Output (returns string/object)
Method Description
.toHex() "#rrggbb"
.toHex8() "#rrggbbaa"
.toRgb() { r, g, b, a }
.toRgbString() "rgb(r, g, b)"
.toHsl() { h, s, l, a }
.toHslString() "hsl(h, s%, l%)"
.toOklch() { l, c, h, a }
.toOklchString() "oklch(l c h)"
.toP3String() "color(display-p3 r g b)"
.toCss(options?) Smart CSS output
Properties
Method Description
.getAlpha() Get alpha value
.getLightness() Get OKLCH lightness
.getChroma() Get OKLCH chroma
.getHue() Get OKLCH hue
.isDark() Lightness < 0.5
.isLight() Lightness >= 0.5
Standalone Functions

All Color methods are also available as standalone, tree-shakeable functions:

import { lighten, darken, mix, contrast, toHex } from "use-color";

License

MIT Junho Yeo

Keywords