Skip to content

Cryptoteep/chroma-flow

Repository files navigation

🎨 chroma-flow

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 | हिन्दी

License: MIT TypeScript Zero Dependencies PRs Welcome Good First Issues


🌱 Mission

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.

✨ Features

  • 🧪 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, SwiftUI Color, Jetpack Compose Color.
  • 🖥️ 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.

📦 Installation

npm install chroma-flow
# or
bun add chroma-flow
# or
pnpm add chroma-flow

Use the CLI globally:

npm install -g chroma-flow
chroma-flow "#6366f1" --format css

…or run it once with bunx / npx:

bunx chroma-flow "#6366f1"

🚀 Quick start

Library

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

CLI

# 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.7

🧠 How it works

chroma-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

📚 API

generatePalette(seed, options?)

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.

checkContrast(foreground, background)

Returns { ratio, passesAANormal, passesAALarge, passesAAANormal, passesAAALarge }.

simulateAll(hex)

Returns an array of { type, hex, original } for all four CVD types.

suggestTextColor(background)

Returns "#000000" or "#ffffff", whichever has higher contrast.

exportPalette(palette, format, name?)

Exports to "css" | "tailwind" | "json" | "scss" | "svg" | "android-xml".

See the full API reference for the complete list.

🤝 Contributing

Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

Please review our Code of Conduct before participating.

🗺️ Roadmap

  • 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

📄 License

MIT © Cryptoteep. See LICENSE.

💛 Acknowledgements

⭐ If chroma-flow helps you build something more accessible, please star the repo — it helps others find it.

About

Accessible color systems, from a single seed. Zero-dependency TypeScript library & CLI for generating WCAG + color-blind-safe palettes using OKLCH.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors