niji
A practical OKLCH color toolkit for Gleam.
It includes:
- OKLCH and RGB types
- OKLCH <-> RGB conversion
- CSS-style gamut mapping for out-of-gamut colors
-
Hex parsing/formatting (
#RGB,#RGBA,#RRGGBB,#RRGGBBAA) - Color manipulation helpers (lighten, saturate, rotate hue, mix)
- Palette helpers (complementary, triadic, analogous, split-complementary)
-
Perceptual distance (
deltaE OK) - WCAG contrast checks
- ANSI terminal color output
-
Integration with
gleam_community/colour
What Is OKLCH?
OKLCH is a perceptual color space based on OKLab, expressed as:
L: lightness (0.0to1.0)C: chroma (color intensity,>= 0.0)H: hue angle in degrees (0to360)
Compared to RGB/HSL, OKLCH is usually easier to reason about when designing palettes:
- equal lightness steps look more visually even,
- hue rotation gives more predictable color relationships,
- chroma control is more intuitive for vivid vs muted colors.
In this library, to_rgb/1 applies CSS-inspired gamut mapping so colors that cannot be displayed in sRGB are adjusted more gracefully than simple channel clipping.
Installation
gleam add niji@1Quick Start
import niji
pub fn main() {
let brand = niji.oklch(0.62, 0.19, 250.0, 1.0)
// Convert to CSS and hex
let css = niji.to_css(brand)
let hex = niji.to_hex(brand)
// Generate a complementary color
let complement = niji.complementary(brand)
// Mix 20% toward complement
let mixed = niji.mix(brand, complement, 0.2)
// Guarantee sRGB-safe output for UI
let safe = niji.gamut_map(mixed)
// Print values in terminal color
let preview = niji.ansi_bg(safe, " OKLCH ")
preview
}Core API
Constructors
oklch(l, c, h, alpha)rgb(r, g, b, alpha)rgb_from_ints(r, g, b, alpha)
All constructors normalize/clamp inputs to safe ranges.
Conversion
to_rgb(color)to_rgb_clamped(color)rgb_to_oklch(color)to_hex(color)rgb_to_hex(color)
Manipulation
lighten,darkensaturate,desaturaterotate_hueset_l,set_c,set_h,set_alphamix
Palette helpers
complementary(color)triadic(color) -> #(Oklch, Oklch)analogous(color, angle) -> #(Oklch, Oklch)split_complementary(color, angle) -> #(Oklch, Oklch)
Gamut + distance
in_gamut(color) -> Boolgamut_map(color) -> Oklchdistance(color1, color2) -> Float
Accessibility
luminance(color)contrast_ratio(color1, color2)wcag_aa(ratio)wcag_aaa(ratio)wcag_aa_large_text(ratio)wcag_aaa_large_text(ratio)
ANSI output
ansi(text, color)(foreground)ansi_bg(text, color)(background)ansi_fg_bg(text, fg, bg)
gleam_community/colour interop
oklch_from_colour(colour)oklch_to_colour(oklch_color)
Examples
Parse hex and rotate hue
import niji
import gleam_community/colour
import gleam/result
pub fn main() {
let assert Ok(rotated) =
colour.from_rgb_hex_string("#3366FF")
|> result.map(niji.oklch_from_colour)
|> result.map(niji.rotate_hue(_, 45.0))
niji.to_css(rotated)
}Check contrast
import niji
pub fn main() {
let text = niji.oklch(0.15, 0.02, 250.0, 1.0)
let bg = niji.oklch(0.96, 0.01, 250.0, 1.0)
let ratio = niji.contrast_ratio(text, bg)
let passes = niji.wcag_aa(ratio)
#(ratio, passes)
}Development
gleam test
gleam runNotes
-
Chroma (
c) is lower-bounded at0.0and not hard-capped. -
Hue wraps around the color wheel (
0..360). to_rgb/1is recommended for display output in sRGB contexts.
Documentation
- Hex package: https://hex.pm/packages/niji
- API docs: https://hexdocs.pm/niji/