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 HEXconsthex=hsl2hex([207,90,54]);// Lighten a color by 20%constlightened=hslColorShift(hex,20);// Generate a full tone scale from the lightened colorconstscale=toneMap(lightened);// Check perceived brightness for contrast decisionsconstbrightnessValue=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?]|undefinedhex2hsl(hex: string): [hue,saturation,lightness,alpha?]|undefinedhex2hwb(hex: string): [hue,whiteness,blackness,alpha?]|undefinedhex2oklab(hex: string): [lightness,a,b,alpha?]|undefinedhex2cmyk(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 |undefinedrgb2hsl(rgb: [red,green,blue,alpha?]): [hue,saturation,lightness,alpha?]|undefinedrgb2hwb(rgb: [red,green,blue,alpha?]): [hue,whiteness,blackness,alpha?]|undefinedrgb2oklab(rgb: [red,green,blue,alpha?]): [lightness,a,b,alpha?]|undefinedrgb2cmyk(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 |undefinedhsl2rgb(hsl: [hue,saturation,lightness,alpha?]): [red,green,blue,alpha?]|undefinedhsl2hwb(hsl: [hue,saturation,lightness,alpha?]): [hue,whiteness,blackness,alpha?]|undefinedhsl2oklab(hsl: [hue,saturation,lightness,alpha?]): [lightness,a,b,alpha?]|undefinedhsl2cmyk(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 |undefinedhwb2rgb(hwb: [hue,whiteness,blackness,alpha?]): [red,green,blue,alpha?]|undefinedhwb2hsl(hwb: [hue,whiteness,blackness,alpha?]): [hue,saturation,lightness,alpha?]|undefinedhwb2oklab(hwb: [hue,whiteness,blackness,alpha?]): [lightness,a,b,alpha?]|undefinedhwb2cmyk(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 |undefinedoklab2rgb(oklab: [lightness,a,b,alpha?]): [red,green,blue,alpha?]|undefinedoklab2hsl(oklab: [lightness,a,b,alpha?]): [hue,saturation,lightness,alpha?]|undefinedoklab2hwb(oklab: [lightness,a,b,alpha?]): [hue,whiteness,blackness,alpha?]|undefinedoklab2cmyk(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 |undefinedcmyk2rgb(cmyk: [cyan,magenta,yellow,black,alpha?]): [red,green,blue,alpha?]|undefinedcmyk2hsl(cmyk: [cyan,magenta,yellow,black,alpha?]): [hue,saturation,lightness,alpha?]|undefinedcmyk2hwb(cmyk: [cyan,magenta,yellow,black,alpha?]): [hue,whiteness,blackness,alpha?]|undefinedcmyk2oklab(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);// lighterhslColorShift('#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);// lighterhwbColorShift('#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 lighteroklabColorShift('#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)consttones=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 uniformityconsttonesOklab=toneMap('#2196f3','oklab');// Custom shift percentages for specific tonesconstcustomTonesMap=toneMap('#2196f3','hsl',{50: 95,// extra light (instead of default 90)100: 80,// slightly less light (instead of default 85)500: 0,// exact base color950: -95,// extra dark (instead of default -97)});// Custom tone steps (not limited to standard 50, 100, 150... 950)constcustomStepsMap=toneMap('#2196f3','oklab',{0: 100,// lightest10: 80,20: 50,30: 0,// base color at step 3040: -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 decisionsif(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 tonescolorify('user123',30);// lower saturation// Custom lightness and alphacolorify('dark-mode',60,30,80);// dark with 80% opacity// Same input always produces same colorcolorify('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 saturationrandomHex(65,80);// with saturation + lightnessrandomHex(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 saturationrandomRgb(65,80);// with saturation + lightnessrandomRgb(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 saturationrandomHsl(65,80);// with saturation + lightnessrandomHsl(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.