Performance optimized useful tools for working with colors. This package is a module of the nextcss project. It provides a comprehensive set of color conversion and manipulation utilities for both browser and Node.js environments with full TypeScript support.
General
Installation ▪ Quick Start ▪ Compatibility ▪ Benchmarks
Conversion Functions
HEX ▪ RGB ▪ HSL ▪ HWB ▪ OKLAB ▪ CMYK
Additional Functions
Color Shift ▪ Tone Map ▪ Brightness ▪ Colorify ▪ Random Colors
Other
If you find this project useful, please consider sponsoring me by:
Donate via GitHub ▪ Donate via PayPal
Give the repo a Star ▪ Follow me on GitHub ▪ Share the project on X
yarn add -D @nextcss/color-toolsnpm i -D @nextcss/color-toolsimport { hsl2hex, hslColorShift, toneMap, brightness } from '@nextcss/color-tools';
// Convert HSL to HEX
const hex = hsl2hex([207, 90, 54]);
// Lighten a color by 20%
const lightened = hslColorShift(hex, 20);
// Generate a full tone scale from the lightened color
const scale = toneMap(lightened);
// Check perceived brightness for contrast decisions
const brightnessValue = brightness(lightened);This package works in both browser and Node.js environments. It includes ESM and CommonJS builds, so both import and require statements work everywhere. Full TypeScript support is included.
Environment: Benchmarks were run on an Intel Core i5-12600K, Node.js v22.16.0, using Benny in a single-threaded environment. Results are measured in operations per second (ops/s) - higher is better.
| Operation | colord | nextcss | Δ |
|---|---|---|---|
| HEX → RGB | 5,899,814 | 16,842,215 | +185% |
| HEX → HSL | 5,819,305 | 13,408,262 | +130% |
| RGB → HEX | 2,938,363 | 62,153,898 | +2016% |
| RGB → HSL | 3,489,380 | 57,524,837 | +1549% |
Note: colord is used as a baseline reference only — it uses a chainable wrapper architecture with additional overhead, so numbers are not a direct apples-to-apples comparison, but give a familiar point of reference.
| From \ To | HEX | RGB | HSL | HWB | OKLAB | CMYK |
|---|---|---|---|---|---|---|
| HEX | — | 17.1M | 13.5M | 13.8M | 4.8M | 13.4M |
| RGB | 61.3M | — | 57.0M | 54.7M | 5.1M | 49.6M |
| HSL | 11.0M | 16.2M | — | 13.5M | 3.9M | 12.4M |
| HWB | 32.1M | 69.5M | 32.7M | — | 4.9M | 29.9M |
| OKLAB | 5.8M | 7.7M | 6.2M | 6.1M | — | 6.1M |
| CMYK | 15.5M | 77.7M | 37.3M | 39.9M | 5.0M | — |
Note: OKLAB conversions are intentionally slower — they involve perceptually uniform color math with significantly more CPU-intensive calculations (non-linear gamma expansion, matrix transforms) compared to geometric color space conversions like HSL or HWB.
| Operation | Fastest | ops/s | Slowest | ops/s |
|---|---|---|---|---|
| Random color generation | HSL | 94,094,590 | OKLAB | 3,559,931 |
| Color shifting | RGB | 7,915,344 | OKLAB | 2,209,869 |
| Tone mapping | RGB | 200,815 | OKLAB | 93,504 |
Note: Tone mapping operates on a fundamentally different scale — it internally uses color shifting, adding cumulative overhead per step — sub-200K ops/s reflects this layered computation rather than a single conversion.
The library provides comprehensive conversion functions between HEX, RGB, HSL, HWB, OKLab, and CMYK color spaces.
Convert from HEX to other color spaces.
hex2rgb(hex: string): [red, green, blue, alpha?] | undefined
hex2hsl(hex: string): [hue, saturation, lightness, alpha?] | undefined
hex2hwb(hex: string): [hue, whiteness, blackness, alpha?] | undefined
hex2oklab(hex: string): [lightness, a, b, alpha?] | undefined
hex2cmyk(hex: string): [cyan, magenta, yellow, black, alpha?] | undefinedReturn values:
hex2rgb:[red (0-255), green (0-255), blue (0-255)]or withalpha (0-100)hex2hsl:[hue (0-360), saturation (0-100), lightness (0-100)]or withalpha (0-100)hex2hwb:[hue (0-360), whiteness (0-100), blackness (0-100)]or withalpha (0-100)hex2oklab:[lightness (0-1), a (-0.4 to 0.4), b (-0.4 to 0.4)]or withalpha (0-100)hex2cmyk:[cyan (0-100), magenta (0-100), yellow (0-100), black (0-100)]or withalpha (0-100)
import { hex2rgb, hex2hsl, hex2hwb, hex2oklab, hex2cmyk } from '@nextcss/color-tools';
hex2rgb('#eee'); // [238, 238, 238]
hex2rgb('#2196f3bf'); // [33, 150, 243, 75]
hex2hsl('#2196f3'); // [207, 90, 54]
hex2hsl('#2196f3bf'); // [207, 90, 54, 75]
hex2hwb('#ff0000'); // [0, 0, 0]
hex2oklab('#269dd9'); // [0.627, 0.224, 0.125]
hex2cmyk('#ff0000'); // [0, 100, 100, 0]Convert from RGB array to other formats.
Array format: [red, green, blue, alpha?]
red,green,blue: 0–255alpha(optional): 0–100
rgb2hex(rgb: [red, green, blue, alpha?]): string | undefined
rgb2hsl(rgb: [red, green, blue, alpha?]): [hue, saturation, lightness, alpha?] | undefined
rgb2hwb(rgb: [red, green, blue, alpha?]): [hue, whiteness, blackness, alpha?] | undefined
rgb2oklab(rgb: [red, green, blue, alpha?]): [lightness, a, b, alpha?] | undefined
rgb2cmyk(rgb: [red, green, blue, alpha?]): [cyan, magenta, yellow, black, alpha?] | undefinedReturn values:
rgb2hex: HEX color string (e.g.'#ff0000')rgb2hsl:[hue (0-360), saturation (0-100), lightness (0-100)]or withalpha (0-100)rgb2hwb:[hue (0-360), whiteness (0-100), blackness (0-100)]or withalpha (0-100)rgb2oklab:[lightness (0-1), a (-0.4 to 0.4), b (-0.4 to 0.4)]or withalpha (0-100)rgb2cmyk:[cyan (0-100), magenta (0-100), yellow (0-100), black (0-100)]or withalpha (0-100)
import { rgb2hex, rgb2hsl, rgb2hwb, rgb2oklab, rgb2cmyk } from '@nextcss/color-tools';
rgb2hex([238, 238, 238]); // '#eeeeee'
rgb2hex([33, 150, 243, 75]); // '#2196f3bf'
rgb2hsl([255, 0, 0]); // [0, 100, 50]
rgb2hsl([255, 0, 0, 50]); // [0, 100, 50, 50]
rgb2hwb([255, 0, 0]); // [0, 0, 0]
rgb2oklab([255, 0, 0]); // [0.627, 0.224, 0.125]
rgb2cmyk([255, 0, 0]); // [0, 100, 100, 0]Convert from HSL array to other formats.
Array format: [hue, saturation, lightness, alpha?]
hue: 0–360 (degrees)saturation: 0–100 (%)lightness: 0–100 (%)alpha(optional): 0–100
hsl2hex(hsl: [hue, saturation, lightness, alpha?]): string | undefined
hsl2rgb(hsl: [hue, saturation, lightness, alpha?]): [red, green, blue, alpha?] | undefined
hsl2hwb(hsl: [hue, saturation, lightness, alpha?]): [hue, whiteness, blackness, alpha?] | undefined
hsl2oklab(hsl: [hue, saturation, lightness, alpha?]): [lightness, a, b, alpha?] | undefined
hsl2cmyk(hsl: [hue, saturation, lightness, alpha?]): [cyan, magenta, yellow, black, alpha?] | undefinedReturn values:
hsl2hex: HEX color string (e.g.'#ff0000')hsl2rgb:[red (0-255), green (0-255), blue (0-255)]or withalpha (0-100)hsl2hwb:[hue (0-360), whiteness (0-100), blackness (0-100)]or withalpha (0-100)hsl2oklab:[lightness (0-1), a (-0.4 to 0.4), b (-0.4 to 0.4)]or withalpha (0-100)hsl2cmyk:[cyan (0-100), magenta (0-100), yellow (0-100), black (0-100)]or withalpha (0-100)
import { hsl2hex, hsl2rgb, hsl2hwb, hsl2oklab, hsl2cmyk } from '@nextcss/color-tools';
hsl2hex([0, 100, 50]); // '#ff0000'
hsl2hex([200, 70, 50]); // '#269dd9'
hsl2rgb([0, 100, 50]); // [255, 0, 0]
hsl2rgb([0, 100, 50, 50]); // [255, 0, 0, 50]
hsl2hwb([0, 100, 50]); // [0, 0, 0]
hsl2oklab([0, 100, 50]); // [0.627, 0.224, 0.125]
hsl2cmyk([0, 100, 50]); // [0, 100, 100, 0]Convert from HWB (Hue, Whiteness, Blackness) array.
Array format: [hue, whiteness, blackness, alpha?]
hue: 0–360 (degrees)whiteness: 0–100 (%)blackness: 0–100 (%)alpha(optional): 0–100
hwb2hex(hwb: [hue, whiteness, blackness, alpha?]): string | undefined
hwb2rgb(hwb: [hue, whiteness, blackness, alpha?]): [red, green, blue, alpha?] | undefined
hwb2hsl(hwb: [hue, whiteness, blackness, alpha?]): [hue, saturation, lightness, alpha?] | undefined
hwb2oklab(hwb: [hue, whiteness, blackness, alpha?]): [lightness, a, b, alpha?] | undefined
hwb2cmyk(hwb: [hue, whiteness, blackness, alpha?]): [cyan, magenta, yellow, black, alpha?] | undefinedReturn values:
hwb2hex: HEX color string (e.g.'#ff0000')hwb2rgb:[red (0-255), green (0-255), blue (0-255)]or withalpha (0-100)hwb2hsl:[hue (0-360), saturation (0-100), lightness (0-100)]or withalpha (0-100)hwb2oklab:[lightness (0-1), a (-0.4 to 0.4), b (-0.4 to 0.4)]or withalpha (0-100)hwb2cmyk:[cyan (0-100), magenta (0-100), yellow (0-100), black (0-100)]or withalpha (0-100)
import { hwb2hex, hwb2rgb, hwb2hsl, hwb2oklab, hwb2cmyk } from '@nextcss/color-tools';
hwb2hex([0, 0, 0]); // '#ff0000'
hwb2rgb([0, 0, 0]); // [255, 0, 0]
hwb2hsl([0, 0, 0]); // [0, 100, 50]
hwb2oklab([0, 0, 0]); // [0.627, 0.224, 0.125]
hwb2cmyk([0, 0, 0]); // [0, 100, 100, 0]Convert from OKLab (perceptual color space) array.
Array format: [lightness, a, b, alpha?]
lightness: 0–1 (perceived brightness)a: −0.4 to 0.4 (green–red axis)b: −0.4 to 0.4 (blue–yellow axis)alpha(optional): 0–100
oklab2hex(oklab: [lightness, a, b, alpha?]): string | undefined
oklab2rgb(oklab: [lightness, a, b, alpha?]): [red, green, blue, alpha?] | undefined
oklab2hsl(oklab: [lightness, a, b, alpha?]): [hue, saturation, lightness, alpha?] | undefined
oklab2hwb(oklab: [lightness, a, b, alpha?]): [hue, whiteness, blackness, alpha?] | undefined
oklab2cmyk(oklab: [lightness, a, b, alpha?]): [cyan, magenta, yellow, black, alpha?] | undefinedReturn values:
oklab2hex: HEX color string (e.g.'#ff0000')oklab2rgb:[red (0-255), green (0-255), blue (0-255)]or withalpha (0-100)oklab2hsl:[hue (0-360), saturation (0-100), lightness (0-100)]or withalpha (0-100)oklab2hwb:[hue (0-360), whiteness (0-100), blackness (0-100)]or withalpha (0-100)oklab2cmyk:[cyan (0-100), magenta (0-100), yellow (0-100), black (0-100)]or withalpha (0-100)
import { oklab2hex, oklab2rgb, oklab2hsl, oklab2hwb, oklab2cmyk } from '@nextcss/color-tools';
oklab2hex([0.627, 0.224, 0.125]); // '#ff0000'
oklab2rgb([0.627, 0.224, 0.125]); // [255, 0, 0]
oklab2hsl([0.627, 0.224, 0.125]); // [0, 100, 50]
oklab2hwb([0.627, 0.224, 0.125]); // [0, 0, 0]
oklab2cmyk([0.627, 0.224, 0.125]); // [0, 100, 100, 0]Convert from CMYK (cyan, magenta, yellow, black) array.
Array format: [cyan, magenta, yellow, black, alpha?]
cyan: 0–100magenta: 0–100yellow: 0–100black: 0–100alpha(optional): 0–100
cmyk2hex(cmyk: [cyan, magenta, yellow, black, alpha?]): string | undefined
cmyk2rgb(cmyk: [cyan, magenta, yellow, black, alpha?]): [red, green, blue, alpha?] | undefined
cmyk2hsl(cmyk: [cyan, magenta, yellow, black, alpha?]): [hue, saturation, lightness, alpha?] | undefined
cmyk2hwb(cmyk: [cyan, magenta, yellow, black, alpha?]): [hue, whiteness, blackness, alpha?] | undefined
cmyk2oklab(cmyk: [cyan, magenta, yellow, black, alpha?]): [lightness, a, b, alpha?] | undefinedReturn values:
cmyk2hex: HEX color string (e.g.'#ff0000')cmyk2rgb:[red (0-255), green (0-255), blue (0-255)]or withalpha (0-100)cmyk2hsl:[hue (0-360), saturation (0-100), lightness (0-100)]or withalpha (0-100)cmyk2hwb:[hue (0-360), whiteness (0-100), blackness (0-100)]or withalpha (0-100)cmyk2oklab:[lightness (0-1), a (-0.4 to 0.4), b (-0.4 to 0.4)]or withalpha (0-100)
import { cmyk2hex, cmyk2rgb, cmyk2hsl, cmyk2hwb, cmyk2oklab } from '@nextcss/color-tools';
cmyk2hex([0, 100, 100, 0]); // '#ff0000'
cmyk2rgb([0, 100, 100, 0]); // [255, 0, 0]
cmyk2hsl([0, 100, 100, 0]); // [0, 100, 50]
cmyk2hwb([0, 100, 100, 0]); // [0, 0, 0]
cmyk2oklab([0, 100, 100, 0]); // [0.627, 0.224, 0.125]Useful tools for color manipulation, including brightness shifting, tone mapping, and random color generation.
Shift the brightness of a color by a percentage. Positive values lighten, negative values darken. Four implementations available using different color spaces — choose based on perceptual needs.
Shift using RGB color space.
rgbColorShift(hex: string, percentage: number): string | undefinedParameters:
hex(string): Input color in HEX formatpercentage(number): Brightness shift in range -100 to 100- Positive values: lighten the color
- Negative values: darken the color
import { rgbColorShift } from '@nextcss/color-tools';
rgbColorShift('#eee', 10); // '#f5f5f5' (lighter)
rgbColorShift('#eee', -10); // '#e5e5e5' (darker)Shift using HSL color space.
hslColorShift(hex: string, percentage: number): string | undefinedParameters:
hex(string): Input color in HEX formatpercentage(number): Lightness shift in range -100 to 100- Positive values: lighten the color
- Negative values: darken the color
import { hslColorShift } from '@nextcss/color-tools';
hslColorShift('#2196f3', 20); // lighter
hslColorShift('#2196f3', -20); // darkerShift using HWB color space.
hwbColorShift(hex: string, percentage: number): string | undefinedParameters:
hex(string): Input color in HEX formatpercentage(number): Brightness shift in range -100 to 100- Positive values: lighten the color
- Negative values: darken the color
import { hwbColorShift } from '@nextcss/color-tools';
hwbColorShift('#2196f3', 15); // lighter
hwbColorShift('#2196f3', -15); // darkerShift using OKLab (best for perceptually uniform results).
oklabColorShift(hex: string, percentage: number): string | undefinedParameters:
hex(string): Input color in HEX formatpercentage(number): Brightness shift in range -100 to 100- Positive values: lighten the color
- Negative values: darken the color
import { oklabColorShift } from '@nextcss/color-tools';
oklabColorShift('#2196f3', 15); // perceptually lighter
oklabColorShift('#2196f3', -15); // perceptually darkerGenerate a complete tonal scale from a base color. Returns an object with tone steps (50–950) mapped to HEX color strings.
toneMap(
hex: string,
mode?: 'rgb' | 'hsl' | 'hwb' | 'oklab',
customTones?: Partial<Record<50 | 100 | 150 | 200 | 250 | 300 | 350 | 400 | 450 | 500 | 550 | 600 | 650 | 700 | 750 | 800 | 850 | 900 | 950, number>>
): Record<string, string> | undefinedParameters:
hex(string): Base color in HEX formatmode(optional): Color space for interpolation —'rgb'|'hsl'|'hwb'|'oklab'(default:'rgb')customTones(optional): Override default tone shift percentages.- Object with numeric tone step keys (any number, e.g. 50, 100, 150, ..., 950, or custom values) and color shift percentages (-100 to 100) as values.
- Each value determines how much to lighten or darken the base color for that tone step.
- If omitted, uses default tone steps (50, 100, 150, ..., 950) with default percentages:
[90, 85, 74, 62, 50, 40, 30, 20, 10, 0, -11, -23, -34, -45, -56, -68, -79, -90, -97]
import { toneMap } from '@nextcss/color-tools';
// Default tone map (uses default shift percentages)
const tones = toneMap('#2196f3');
console.log(tones);
// {
// 50: '#e3f2fd', (tone 50, +90% lighter)
// 100: '#bbdefb', (tone 100, +85% lighter)
// 150: '#90caf9',
// ...
// 500: '#2196f3', (tone 500, no shift)
// ...
// 950: '#0d47a1' (tone 950, -97% darker)
// }
// Using OKLab for better perceptual uniformity
const tonesOklab = toneMap('#2196f3', 'oklab');
// Custom shift percentages for specific tones
const customTonesMap = toneMap('#2196f3', 'hsl', {
50: 95, // extra light (instead of default 90)
100: 80, // slightly less light (instead of default 85)
500: 0, // exact base color
950: -95, // extra dark (instead of default -97)
});
// Custom tone steps (not limited to standard 50, 100, 150... 950)
const customStepsMap = toneMap('#2196f3', 'oklab', {
0: 100, // lightest
10: 80,
20: 50,
30: 0, // base color at step 30
40: -50,
50: -100, // darkest
});Calculate the perceived brightness of a color as a percentage (0–100). Useful for determining text contrast or applying adaptive styling.
brightness(hex: string): number | undefinedimport { brightness } from '@nextcss/color-tools';
brightness('#000000'); // 0 (darkest)
brightness('#ffffff'); // 100 (lightest)
brightness('#269dd9'); // ~53 (neutral)
// Use for contrast decisions
if (brightness(color) < 50) {
textColor = '#ffffff'; // light text on dark bg
} else {
textColor = '#000000'; // dark text on light bg
}Generate a consistent, deterministic color from any string input (e.g., usernames, IDs). Outputs the same color for the same input every time.
colorify(
str: string,
saturation?: number,
lightness?: number,
alpha?: number
): string | undefinedParameters:
str(string): Input stringsaturation(optional): 0–100 (default: 50)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100 (default: 100)
import { colorify } from '@nextcss/color-tools';
colorify('alice@example.com'); // '#40bf79' (consistent)
colorify('bob@example.com'); // '#ff6b9d' (different)
// Adjust saturation for muted tones
colorify('user123', 30); // lower saturation
// Custom lightness and alpha
colorify('dark-mode', 60, 30, 80); // dark with 80% opacity
// Same input always produces same color
colorify('alice') === colorify('alice'); // trueGenerate random colors in any format. All random functions accept optional saturation, lightness, and alpha parameters.
randomHex(saturation?: number, lightness?: number, alpha?: number): string | undefinedParameters:
saturation(optional): 0–100 (default: 70)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100
import { randomHex } from '@nextcss/color-tools';
randomHex(); // '#7de889' (random)
randomHex(50); // with saturation
randomHex(65, 80); // with saturation + lightness
randomHex(65, 80, 50); // with alpha (50%)randomRgb(saturation?: number, lightness?: number, alpha?: number): [red, green, blue, alpha?] | undefinedParameters:
saturation(optional): 0–100 (default: 70)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100
import { randomRgb } from '@nextcss/color-tools';
randomRgb(); // [232, 193, 125]
randomRgb(50); // with saturation
randomRgb(65, 80); // with saturation + lightness
randomRgb(65, 80, 50); // with alpha (50%)randomHsl(saturation?: number, lightness?: number, alpha?: number): [hue, saturation, lightness, alpha?] | undefinedParameters:
saturation(optional): 0–100 (default: 70)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100
import { randomHsl } from '@nextcss/color-tools';
randomHsl(); // [294, 70, 50]
randomHsl(50); // with saturation
randomHsl(65, 80); // with saturation + lightness
randomHsl(65, 80, 50); // with alpha (50%)randomHwb(saturation?: number, lightness?: number, alpha?: number): [hue, whiteness, blackness, alpha?] | undefinedParameters:
saturation(optional): 0–100 (default: 70)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100
import { randomHwb } from '@nextcss/color-tools';
randomHwb(); // [45, 10, 20]
randomHwb(60, 50, 75); // with parametersrandomOklab(saturation?: number, lightness?: number, alpha?: number): [lightness, a, b, alpha?] | undefinedParameters:
saturation(optional): 0–100 (default: 70)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100
import { randomOklab } from '@nextcss/color-tools';
randomOklab(); // [0.627, 0.224, 0.125]
randomOklab(70, 50, 90); // with parametersrandomCmyk(saturation?: number, lightness?: number, alpha?: number): [cyan, magenta, yellow, black, alpha?] | undefinedParameters:
saturation(optional): 0–100 (default: 70)lightness(optional): 0–100 (default: 50)alpha(optional): 0–100
import { randomCmyk } from '@nextcss/color-tools';
randomCmyk(); // [0, 100, 100, 0]
randomCmyk(70, 50, 90); // with parametersSee the project Code of Conduct, Contributing, and Security Policy for details.
MIT License © 2022 Zsolt Tövis
If you find this project useful, please consider sponsoring me by: Donate via GitHub / Donate via PayPal / Give the repo a Star / Follow me on GitHub / Share the project on X
Made with ❤️ for developers who love clean APIs and don't need a heavy library with tons of dependencies.