Accessible color systems, from a single seed.
A zero-dependency TypeScript library & CLI that generates perceptually-uniform, WCAG-compliant, color-blind-safe color palettes using the modern OKLCH color space.
English | हिन्दी
Make accessible design free and effortless for everyone.
Color accessibility shouldn't be a premium feature locked behind paid design
tools. 1 in 12 men and 1 in 200 women live with some form of color vision
deficiency, yet most generated palettes are never checked against it.
chroma-flow ships WCAG 2.1 contrast checking and color-blind simulation in a
tiny, dependency-free package so that every developer can build inclusive
interfaces — no SaaS, no signup, no tracking.
This is a non-commercial, community-driven project. It will never be paywalled, ad-supported, or acquired.
- 🧪 OKLCH-based generation — perceptually uniform ramps that look balanced to the human eye.
- ♿ WCAG 2.1 contrast checking — AA / AAA conformance for normal and large text.
- 📏 APCA contrast (WCAG 3 candidate) — signed perceptual Lc values, more accurate than WCAG 2.1 for dark themes.
- 🌗 Light + dark theme generation — a coordinated semantic theme pair (background, surface, text, primary, accent, success/warning/danger) from one seed, with a per-role WCAG + APCA audit.
- 🎨 Color harmonies — complementary, analogous, triadic, tetradic, split-complementary, and monochromatic schemes derived from a seed.
- 📐 Delta-E ∆E76 / ∆E94 / ∆E2000 — three CIE color-difference metrics (from fast Euclidean to perceptually-accurate CIEDE2000) with a human-readable band and a nearest-color finder.
- 🎛️ Color manipulation — mix, lighten, darken, saturate, rotate hue, complement, invert, and random-seed generation in OKLCH.
- 🧰 Palette utilities — interpolate midpoints, sort by lightness/chroma, reverse, or emit a CSS gradient.
- 📥 Palette import & seed inference — parse an existing CSS / Tailwind / JSON palette back into a chroma-flow seed, so you can reverse-engineer a design system and keep tweaking.
- ♿ Accessible pair finder — discover WCAG-conformant foreground/background pairs from a palette, audit every stop's text accessibility, and list the stops that pass a given level.
- 🎨 WCAG 2.2 non-text contrast — check the 3:1 threshold for UI components, borders, icons, and focus indicators (SC 1.4.11).
- 🌈 Wide-gamut (Display-P3) support — convert to/from CSS
color(display-p3 ...), detect out-of-sRGB-gamut colors, and clamp back to sRGB. - 📋 Full accessibility report — one call combining WCAG 2.1 text, WCAG 2.2 non-text, and gamut loss into a per-stop table with an overall score.
- 👁️ Color-blind simulation — protanopia, deuteranopia, tritanopia, achromatopsia.
- 🎯 Smart text-color suggestion — auto-pick black or white text for any background.
- 📤 Eight export formats — CSS variables, Tailwind config, JSON, SCSS, SVG swatch sheets, Android
colors.xml, SwiftUIColor, Jetpack ComposeColor. - 🖥️ CLI + library — use it in code or from the terminal.
- 🪶 Zero runtime dependencies — auditable in an afternoon, ships tiny.
- 🌲 Tree-shakeable ESM — import only what you use.
npm install chroma-flow
# or
bun add chroma-flow
# or
pnpm add chroma-flowUse the CLI globally:
npm install -g chroma-flow
chroma-flow "#6366f1" --format css…or run it once with bunx / npx:
bunx chroma-flow "#6366f1"import {
generatePalette,
checkContrast,
suggestTextColor,
simulateAll,
exportPalette,
} from "chroma-flow";
// 1. Generate a full 50–950 palette from one seed.
const palette = generatePalette("#6366f1");
console.log(palette[500]); // "#6265f0"
// 2. Export it to CSS variables.
const css = exportPalette(palette, "css", "primary");
// 3. Pick the most readable text color for a background.
const text = suggestTextColor(palette[600]); // "#ffffff"
// 4. Check WCAG conformance.
const result = checkContrast(text, palette[600]);
console.log(result.ratio); // 8.48
console.log(result.passesAAANormal); // true
// 5. Preview the seed under color vision deficiencies.
simulateAll(palette[500]).forEach((p) =>
console.log(p.type, p.hex)
);
// 6. (v0.2) Check APCA perceptual contrast (WCAG 3 candidate).
import { checkAPCA } from "chroma-flow";
const apca = checkAPCA("#ffffff", palette[600]);
console.log(apca.Lc); // -87.6 (negative = light text on dark)
console.log(apca.passesBodyText); // true (|Lc| ≥ 75)
// 7. (v0.2) Generate a coordinated light + dark theme.
import { generateTheme, themeToCSS } from "chroma-flow";
const theme = generateTheme("#6366f1");
console.log(theme.light.primary); // "#3f37bb"
console.log(theme.dark.primary); // "#8b95ff"
const themeCss = themeToCSS(theme, "brand"); // :root {…} .dark {…}
// 8. (v0.3) Generate a color harmony.
import { generateHarmony } from "chroma-flow";
const triad = generateHarmony("#6366f1", "triadic");
triad.colors.forEach((c) => console.log(c.role, c.hex)); // base, triad-1, triad-2
// 9. (v0.3) Measure the perceptual difference between two colors.
import { checkDeltaE, nearestColor, deltaE76 } from "chroma-flow";
const d = checkDeltaE("#6366f1", "#5b5cf0"); // { deltaE: 3.17, band: "noticeable", belowJND: false }
const nearest = nearestColor("#6366f1", ["#ef4444", "#3f37bb", "#10b981"]);
deltaE76("#6366f1", "#5b5cf0"); // ~6.71 (v0.5 — the simpler ∆E76 metric)
// 10. (v0.4) Manipulate colors in OKLCH.
import { mixColors, lighten, rotateHue, randomSeed } from "chroma-flow";
mixColors("#6366f1", "#f59e0b", 0.5); // "#e95ea0"
lighten("#6366f1", 0.15); // "#8d97ff"
rotateHue("#6366f1", 120); // "#d93a00"
randomSeed(); // e.g. "#a952ba"
// 11. (v0.4) Work with full palettes.
import { interpolatePalette, reversePalette, paletteToGradient } from "chroma-flow";
const dense = interpolatePalette(palette); // 21 steps (11 + 10 mids)
const inverted = reversePalette(palette); // 50 ↔ 950
const gradient = paletteToGradient(palette); // "linear-gradient(to right, …)"
// 12. (v0.6) Import an existing palette and infer its seed.
import { importAndInfer } from "chroma-flow";
const css = `:root { --brand-500: #6366f1; --brand-600: #3f37bb; }`;
const { inferred } = importAndInfer(css);
console.log(inferred.seed); // "#6366f1"
console.log(inferred.averageDeltaE); // ~0.19 (lower = better fit)
console.log(inferred.palette[500]); // regenerated from the inferred seed
// 13. (v0.7) Find WCAG-conformant pairs and audit the palette.
import { findAccessiblePair, paletteAccessibilityMatrix } from "chroma-flow";
const pair = findAccessiblePair(palette, "AAA");
// { foreground: "#ffffff", background: "#0c033d", ratio: 19.16, passesAAA: true }
const matrix = paletteAccessibilityMatrix(palette);
// [{ stop: 50, recommendedText: "#000000", band: "AAA" }, …]
// 14. (v0.8) Check WCAG 2.2 non-text contrast (3:1 for UI components).
import { checkNonTextContrast, paletteNonTextMatrix } from "chroma-flow";
checkNonTextContrast("#6366f1", "#ffffff", "border");
// { ratio: 4.47, passes: true, band: "Pass", kind: "border" }
paletteNonTextMatrix(palette, "#ffffff");
// [{ stop: 50, color: "#e8f5ff", ratio: 1.11, passes: false }, …]
// 15. (v0.9) Convert to CSS display-p3 wide-gamut notation.
import { toP3String, analyzeGamut } from "chroma-flow";
toP3String("#10b981"); // "color(display-p3 0.090178 0.461936 0.251553)"
analyzeGamut("#10b981"); // { p3String, inSRGB: true, inP3: true, gamutLoss: 0 }
// 16. (v1.0) Get a unified full accessibility report.
import { fullAccessibilityReport } from "chroma-flow";
const report = fullAccessibilityReport(palette, "#ffffff", "AAA", { seed: "#6366f1" });
console.log(report.overallScore); // 0.82
console.log(report.textPassing); // 10/11# Generate a palette
chroma-flow "#6366f1"
# 50 #e8f5ff █
# 100 #deecff ██
# 200 #cad9ff ████
# ...
# Export as CSS variables
chroma-flow "#6366f1" --format css --name primary
# Export as SwiftUI / Jetpack Compose (v0.2)
chroma-flow "#6366f1" --format swift --name brand
chroma-flow "#6366f1" --format compose --name Brand
# Check WCAG 2.1 contrast against a foreground color
chroma-flow "#f59e0b" --contrast "#000000"
# Check APCA perceptual contrast (v0.2)
chroma-flow "#10b981" --apca "#ffffff"
# Generate a coordinated light + dark theme pair (v0.2)
chroma-flow "#6366f1" --theme --name brand
# Generate a color harmony (v0.3)
chroma-flow "#6366f1" --harmony triadic
# Measure the perceptual difference between two colors (v0.3)
chroma-flow "#6366f1" --delta-e "#5b5cf0"
# …using the simpler ∆E76 metric (v0.5)
chroma-flow "#6366f1" --delta-e "#5b5cf0" --delta-method 76
# Mix, rotate, complement, lighten (v0.4)
chroma-flow "#6366f1" --mix "#f59e0b" --mix-amount 0.3
chroma-flow "#6366f1" --rotate 120
chroma-flow "#6366f1" --complement
chroma-flow "#6366f1" --lighten 0.15
chroma-flow --random
# Palette utilities (v0.4)
chroma-flow "#6366f1" --interpolate
chroma-flow "#6366f1" --reverse
chroma-flow "#6366f1" --gradient
# Import an existing palette and infer its seed (v0.6)
chroma-flow --import ":root { --brand-500: #6366f1; }" --infer-seed
# Find WCAG-conformant pairs and audit the palette (v0.7)
chroma-flow "#6366f1" --pairs --level AAA
chroma-flow "#6366f1" --matrix
# Audit WCAG 2.2 non-text (3:1) contrast (v0.8)
chroma-flow "#6366f1" --nontext --nontext-bg "#ffffff"
# Emit the palette as CSS display-p3 wide-gamut (v0.9)
chroma-flow "#6366f1" --p3
# Get a unified full accessibility report (v1.0)
chroma-flow "#6366f1" --report --report-bg "#ffffff" --level AAA
# Simulate color vision deficiencies
chroma-flow "#6366f1" --cvd
# Tweak the ramp
chroma-flow "#6366f1" --distribution linear --hue-shift -20 --chroma-falloff 0.7chroma-flow converts your seed color into the OKLCH color space (a
cylindrical form of OKLab), then walks a lightness ramp across 11 stops while
modulating chroma with a falloff curve and an optional hue shift. The result is
a smooth, natural-feeling scale where every stop stays inside the sRGB gamut.
OKLCH is chosen over HSL because HSL lightness is not perceptually uniform — a "50% lightness" yellow is far brighter to the eye than a "50% lightness" blue. OKLCH fixes this, so ramps actually look balanced.
seed hex
│
▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ sRGB → │ ─► │ lightness │ ─► │ chroma │
│ OKLCH │ │ ramp (11) │ │ falloff │
└──────────┘ └──────────────┘ └──────┬───────┘
│
┌───────────────┴───────────────┐
▼ ▼
WCAG contrast checks CVD simulation
(AA / AAA) (4 conditions)
│ │
└───────────────┬───────────────┘
▼
CSS / Tailwind / JSON / SCSS / SVG / XML
Returns a Record<50|100|…|950, string> palette.
| Option | Type | Default | Description |
|---|---|---|---|
distribution |
"linear" | "perceptual" |
"perceptual" |
Lightness distribution across the ramp. |
chromaFalloff |
number (0–1) |
0.5 |
Vividness of mid-tones vs. extremes. |
hueShift |
number (degrees) |
0 |
Hue drift across the ramp. |
maxChroma |
number |
0.32 |
Clamp to avoid out-of-gamut colors. |
Returns { ratio, passesAANormal, passesAALarge, passesAAANormal, passesAAALarge }.
Returns an array of { type, hex, original } for all four CVD types.
Returns "#000000" or "#ffffff", whichever has higher contrast.
Exports to "css" | "tailwind" | "json" | "scss" | "svg" | "android-xml".
See the full API reference for the complete list.
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
- 🐛 Found a bug? Open an issue.
- 💡 Have an idea? Suggest a feature.
- 🔧 Want to code? Grab a
good first issueand read CONTRIBUTING.md.
Please review our Code of Conduct before participating.
-
APCA contrast support (WCAG 3 candidate)— shipped in v0.2.0 -
Theme generation (light + dark from one seed)— shipped in v0.2.0 -
Swift + Jetpack Compose exporters— shipped in v0.2.0 -
Color harmonies (complementary / triadic / analogous …)— shipped in v0.3.0 -
Delta-E ∆E2000 color difference helper— shipped in v0.3.0 -
Color manipulation & palette utilities— shipped in v0.4.0 -
∆E76 and ∆E94 metrics— shipped in v0.5.0 -
Palette import & seed inference— shipped in v0.6.0 -
Accessible pair finder & palette matrix— shipped in v0.7.0 -
WCAG 2.2 non-text contrast— shipped in v0.8.0 -
Wide-gamut (Display-P3) support— shipped in v0.9.0 -
Full accessibility report— shipped in v1.0.0 - Live web playground (in-repo, deployed to GitHub Pages)
- ESM + CJS dual build
- Figma plugin
MIT © Cryptoteep. See LICENSE.
- Björn Ottosson for the OKLab / OKLCH color space.
- Machado et al. (2009) for the color vision deficiency simulation matrices.
- The W3C for the WCAG 2.1 contrast specification.
⭐ If chroma-flow helps you build something more accessible, please star the repo — it helps others find it.