A TypeScript-first, OKLCH-based, framework-agnostic color library
that validates color strings at compile time.
Never ship an invalid color string again.
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.
pnpm add use-colornpm install use-coloryarn add use-colorimport { 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 });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);const result = color("#e11d48")
.lighten(0.1)
.saturate(0.2)
.rotate(15)
.alpha(0.9)
.toHex();
// "#ff2d5c"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)"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' | ...
}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(); // trueBuilt-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 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.
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.
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)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);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 |
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| 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 |
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-coloruses 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| Function | Description |
|---|---|
color(input) |
Create Color instance (throws on invalid) |
tryColor(input) |
Safe parsing, returns Result<Color, ColorParseError> |
| 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 |
| 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 |
| 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 |
All Color methods are also available as standalone, tree-shakeable functions:
import { lighten, darken, mix, contrast, toHex } from "use-color";MIT Β© Junho Yeo
