<p align="center">
<img src="https://raw.githubusercontent.com/kwando/oklch/main/logo.png" alt="niji logo" width="300">
</p>
# niji
[](https://hex.pm/packages/niji)
[](https://hexdocs.pm/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.0` to `1.0`)
- `C`: chroma (color intensity, `>= 0.0`)
- `H`: hue angle in degrees (`0` to `360`)
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
```sh
gleam add niji@1
```
## Quick Start
```gleam
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`, `darken`
- `saturate`, `desaturate`
- `rotate_hue`
- `set_l`, `set_c`, `set_h`, `set_alpha`
- `mix`
### Palette helpers
- `complementary(color)`
- `triadic(color) -> #(Oklch, Oklch)`
- `analogous(color, angle) -> #(Oklch, Oklch)`
- `split_complementary(color, angle) -> #(Oklch, Oklch)`
### Gamut + distance
- `in_gamut(color) -> Bool`
- `gamut_map(color) -> Oklch`
- `distance(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
```gleam
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
```gleam
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
```sh
gleam test
gleam run
```
## Notes
- Chroma (`c`) is lower-bounded at `0.0` and not hard-capped.
- Hue wraps around the color wheel (`0..360`).
- `to_rgb/1` is recommended for display output in sRGB contexts.
## Documentation
- Hex package: <https://hex.pm/packages/niji>
- API docs: <https://hexdocs.pm/niji/>