Loading repository dataβ¦
Loading repository dataβ¦
junhoyeo / repository
πΌ A TypeScript-first, OKLCH-based, framework-agnostic color library that validates color strings at compile time
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
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-color
npm install use-color
yarn add use-color
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 });
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();
// "#ff4b2f"
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.22, s: 0.91, l: 0.6, a: 1 } (values rounded here)
c.toHslString(); // "hsl(217.22, 91%, 60%)"
c.toHslaString(); // "hsla(217.22, 91%, 60%, 1)"
c.toHslModern(); // "hsl(217.22 91% 60%)"
// OKLCH (perceptually uniform)
c.toOklch(); // { l: 0.623, c: 0.188, h: 259.81, a: 1 } (values rounded here)
c.toOklchString(); // "oklch(0.623 0.188 259.815)"
// Display P3 (wide gamut)
c.toP3String(); // "color(display-p3 0.3047 0.5035 0.9338)"
// CSS (smart default)
c.toCss(); // "#3b82f6"
c.toCss({ format: "oklch" }); // "oklch(0.623 0.188 259.815)"
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.623 (OKLCH lightness, rounded here)
c.getChroma(); // 0.188 (OKLCH chroma, rounded here)
c.getHue(); // 259.81 (OKLCH hue, rounded here)
c.isDark(); // false
c.isLight(); // true
Built-in WCAG 2.1 contrast checking and auto-adjustment.
import { contrast, isReadable, ensureContrast, luminance, toHex } from "use-color";
const text = "#374151";
const background = "#ffffff";
// Relative luminance (WCAG formula)
luminance(text); // ~0.052
luminance(background); // 1.0
// Contrast ratio (1-21)
contrast(text, background); // ~10.31
// Readability checks
isReadable(text, background); // true (default: AA 4.5:1)
isReadable(text, background, { level: "AAA" }); // true (7:1)
isReadable(text, background, { level: "AA", isLargeText: true }); // true (3:1)
// Auto-adjust for accessibility
const adjusted = ensureContrast("#888888", background, 4.5);
toHex(adjusted); // "#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"); // 72 (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.25 150)");
// Check gamut: outside sRGB, but representable in Display P3
isInP3Gamut(vibrant); // true (P3 is ~25% larger than sRGB)
// Output for modern displays
vibrant.toP3String(); // "color(display-p3 0.1612 0.7599 0.3015)"
// Fallback for older displays
vibrant.toHex(); // "#00c248" (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";
Note:
use-color/coreexcludes the standalone a11y functions and the standalone named-color/P3 parse helpers (parseNamed,parseP3, etc.). TheColorclass itself still transitively supports parsing named colors (color('coral')) and producing Display P3 output (c.toP3String()), since that logic lives insideColor's own parsing/formatting pipeline rather than a separately tree-shakeable module. Import fromuse-color/namesoruse-color/p3only if you need the standalone functions.
| 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 | β οΈ | β οΈ |
- 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)- 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);
- 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) |