This comprehensive guide covers everything from basic color manipulation to advanced HDR tone mapping and accessibility analysis using @open-kappa/colors.
npm install @open-kappa/colors
# or
bun add @open-kappa/colors
import {
Harmony,
Oklch,
Rgb,
Wcag
} from "@open-kappa/colors"
// 1. Create a color (Standard sRGB)
const red = Rgb.fromComponents([255, 0, 0, 1])
// 2. Convert to a perceptual space (Oklch)
const oklch = red.to(Oklch)
// 3. Generate a color harmony (Analogous palette)
const palette = Harmony.analogous(red, 5)
// 4. Check accessibility against transparent white
const contrast = Wcag.contrast(
red,
Rgb.fromComponents([255, 255, 255, 0])
)
Before diving deeper, understanding these five fundamental principles is crucial:
setR()) returns a new instance; the original remains unchanged.Rgb -> Xyz -> Hsl).to()) are mathematical. They do not perform automatic gamut mapping or tone mapping unless explicitly requested via specific APIs.ColorMeta), enabling powerful manipulations even without knowing the specific color space type at compile time.You can create colors using type-safe static methods, component arrays, factories, or by parsing standard string formats.
import {
Hsl,
Oklch,
P3,
Rgb
} from "@open-kappa/colors"
// RGB (0-255)
const red = Rgb.fromComponents([255, 0, 0, 1])
// HSL (H: 0-360, S: 0-100, L: 0-100)
const blue = Hsl.fromComponents([240, 100, 50, 1])
// OKLCH (L: 0-1, C: 0-0.4, H: 0-360)
const perceptual = Oklch.fromComponents([0.6, 0.2, 180, 1])
// Display P3 using a JSON object (explicit keys)
const p3Color = P3.fromJSON({
"alpha": 1, "b": 0, "g": 1, "r": 0
})
import {
Css,
Hex,
Standards
} from "@open-kappa/colors"
// From Hex string
const fromHex = Hex.fromRgbHexString("#ff0000")
// From CSS functional notation
const fromCss = Css.fromCssString("rgb(255 0 0 / 0.5)")
// From Named Colors
const fromName = Standards.fromName("cornflowerblue")
Useful when the color space is determined at runtime.
import {ColorFactory} from "@open-kappa/colors"
const factoryColor = ColorFactory.fromColorSpace("lab", [50, -20, 20, 1])
Convert easily between color spaces. The library handles the math via the XYZ hub.
import {
Base,
Lab,
Oklch,
Rgb
} from "@open-kappa/colors"
const rgb = Rgb.fromComponents([255, 0, 0, 1])
// 1. Standard Geometric Conversion
const oklch = rgb.to(Oklch)
const lab = rgb.to(Lab)
console.log(oklch.components)
// Output: ~[0.62, 0.25, 29.2, 1] (Lightness, Chroma, Hue, Alpha)
// 2. Advanced Conversion (White Balance & Tone Mapping)
// Use Base.convert for fine-grained control
const sophisticated = Base.convert(rgb, Oklch, {
"toneMapping": "agX",
"whiteBalance": "cat02"
})
// 3. Range Checks
const isSdr = rgb.isInSdr() // True
const isHdr = rgb.isInHdr() // False
const clipped = rgb.clipSdr() // Clamps values to valid range
Traditional spaces like RGB or HSL are not perceptually uniform.
import {
Oklch,
Rgb,
Saturation
} from "@open-kappa/colors"
const rgb = Rgb.fromComponents([255, 0, 0, 1])
// ❌ BAD (RGB): Linearly scaling RGB channels desaturates the color
const muddyDark = rgb.setR(127).setG(0)
.setB(0)
// ✅ GOOD (Oklch): Adjusting Lightness preserves Chroma and Hue
const color = rgb.to(Oklch)
const niceDark = color.setL(0.5)
// Adjust Chroma (Vibrancy)
const vibrant = color.setC(0.3)
const muted = Saturation.decrease(color, 0.5)
// Shift Hue (Complementary palette example)
const complementary = color.setH(color.h + 180)
Linear interpolation in RGB creates "dead zones" (gray areas). Perceptual interpolation maintains vibrancy.
import {
Generator,
Interpolation,
Oklab,
Oklch,
Rgb
} from "@open-kappa/colors"
const start = Rgb.fromComponents([0, 0, 255, 1]) // Blue
const end = Rgb.fromComponents([255, 255, 0, 1]) // Yellow
// 1. Gradient Generation
// Uses Oklab for a vibrant mix passing through neutral tones correctly
const gradient = Generator.gradient(
100,
start.to(Oklab),
end.to(Oklab),
Interpolation.linear
)
// 2. Direct Interpolation
const midPoint = Interpolation.bezier(
start.to(Oklab),
end.to(Oklab),
0.5
)
// 3. Data Visualization Scales
// Generate distinct colors for charts using Golden Ratio
const chartColors = Generator.distinct(10, {
"chroma": 0.14,
"useGoldenRatio": true
})
High Dynamic Range (HDR) colors often exceed the visible range of standard monitors. Tone mapping compresses this range smoothly (cinematographic approach) rather than clipping it harshly.
import {
LinearRgb,
Oklch,
Rec2020,
ToneMapper
} from "@open-kappa/colors"
// An HDR color (e.g., sun reflection, 10x brighter than white)
const sun = Rec2020.fromComponents([10, 10, 8, 1])
// ❌ Naive Clipping: Destroys details in bright areas
const sdrBad = sun.clipSdr()
// ✅ Reinhard: Compresses light softly (General purpose)
const sdrReinhard = ToneMapper.reinhard(sun.to(Oklch))
// ✅ ACES Filmic: Standard cinematic look (Narkowicz curve)
const sdrFilmic = ToneMapper.acesRgb(sun.to(LinearRgb))
// ✅ AgX: Next-gen photorealistic tone mapping (prevents hue skews)
const sdrAgx = ToneMapper.agX(sun.to(LinearRgb), 1.0, "punchy")
Ensure your UI meets accessibility standards and simulates how users with color vision deficiencies see your content.
import {
ColorDifference,
Cvd,
Lab,
LinearRgb,
Rgb,
Wcag
} from "@open-kappa/colors"
const text = Rgb.fromComponents([0, 0, 0, 1]).toXyz()
const bg = Rgb.fromComponents([255, 255, 255, 1]).toXyz()
// 1. Contrast Ratio & WCAG Compliance
const ratio = Wcag.contrast(bg, text) // 21:1
const meetsAA = Wcag.isReadable(ratio, {"level": "AA", "size": "normal"})
const meetsAAA = Wcag.isReadable(ratio, {"level": "AAA"})
// 2. Advanced Distance (DeltaE 2000)
// Measures perceived difference between colors
const deltaE = ColorDifference.deltaE2000(text.to(Lab), bg.to(Lab))
// 3. Color Vision Deficiency (Color Blindness) Simulation
const source = Rgb.fromComponents([255, 0, 0, 1]).to(LinearRgb)
// Simulate Protanopia (Red-blind)
const simulated = Cvd.simulate(source, "protanopia")
// Attempt to correct colors for Deuteranopia (Green-blind)
const corrected = Cvd.correct(source, "deuteranopia")
Extract palettes from raw image data using K-Means clustering.
import {
Extraction,
Hsl,
Palette,
Rgb
} from "@open-kappa/colors"
// Assume imageData is a Uint8ClampedArray from a Canvas or Image Buffer
const imageData: Uint8ClampedArray = new Uint8ClampedArray([/* ... */])
// Extract 5 dominant colors
const clusters = Extraction.kMeansClustering(
imageData,
{"count": 5}
)
// Generate a dynamic palette from the primary dominant color
const primaryDom = clusters[0]
const uiPalette = Palette.dynamic(primaryDom.to(Hsl))
If you need to work with colors generically (e.g., a UI component handling any color space), you can use the Metadata API.
import {
type Color,
Rgb
} from "@open-kappa/colors"
function adjustLightnessGeneric(color: Color, amount: number): Color
{
// Check if the color space supports Lightness
if (color.lightnessIndex !== null)
{
// TrySetLightness returns a new instance of the same type
return color.trySetLightness(amount)
}
// Fallback: Convert to a space that definitely has lightness
// (Note: This changes the type of the color)
const rgb = color.to(Rgb)
// Rgb doesn't have direct lightness, so we might convert
// to HSL or Oklch here...
return rgb
}
// Inspect component names
const myColor = Rgb.fromComponents([0, 0, 0, 1])
console.log(myColor.meta.componentNames()) // ["r", "g", "b", "alpha"]