This is the complete public API of chroma-flow. All functions are
tree-shakeable ESM exports and have zero runtime dependencies.
- Palette generation
- Color space conversions
- WCAG contrast
- APCA contrast
- Color vision deficiency
- Text color suggestion
- Semantic themes
- Color harmonies
- Delta-E ∆E2000
- Color manipulation
- Palette utilities
- Palette import & seed inference
- Accessible pairs
- WCAG 2.2 non-text contrast
- Wide-gamut (Display-P3)
- Full accessibility report
- Exporters
- Types
Generate a full 50–950 palette from a seed hex color.
const palette = generatePalette("#6366f1");
// { 50: "#e8f5ff", 100: "#deecff", ..., 950: "#0c033d" }Options
| Option | Type | Default |
|---|---|---|
distribution |
"linear" | "perceptual" |
"perceptual" |
chromaFalloff |
number (0–1) |
0.5 |
hueShift |
number (degrees) |
0 |
maxChroma |
number |
0.32 |
Generate multiple named palettes at once.
const system = generatePalettes({
primary: "#6366f1",
success: "#10b981",
warning: "#f59e0b",
});Returns the ordered array of palette stops: [50, 100, 200, ..., 950].
Convert between hex strings and the OKLCH color space.
Lower-level conversion helpers.
Normalize any hex form (#abc, #aabbcc) to a 7-character #aabbcc string.
WCAG 2.1 relative luminance of an RGB color (0–1).
Returns the WCAG contrast ratio (1–21).
Full conformance check:
{
ratio: 8.48,
passesAANormal: true,
passesAALarge: true,
passesAAANormal: true,
passesAAALarge: true,
}Boolean check for "AA" or "AAA" (normal text).
Formats a ratio for display: 4.53 → "4.53:1".
APCA (Accessible Perceptual Contrast Algorithm) is the WCAG 3 candidate contrast method. It is more perceptually accurate than the WCAG 2.1 ratio — especially for dark backgrounds, where WCAG 2.1 overestimates readability.
Returns a signed Lc value (roughly −107 to +106):
- Positive = dark text on a light background.
- Negative = light text on a dark background.
- Magnitude = perceived contrast strength.
apcaContrast("#000000", "#ffffff"); // +106
apcaContrast("#ffffff", "#000000"); // -107Full APCA check with conformance helpers:
{
Lc: -87.6,
magnitude: 87.6,
recommendedMinSize: 12,
passesBodyText: true, // |Lc| ≥ 75
passesLargeText: true, // |Lc| ≥ 60
passesNonText: true, // |Lc| ≥ 45
}Formats an Lc value: -87.6 → "Lc -87.6".
Returns "#000000" or "#ffffff" based on APCA magnitude.
Simulate one CVD type. type ∈ "protanopia" | "deuteranopia" | "tritanopia" | "achromatopsia".
Simulate all four CVDs, returning [{ type, hex, original }, ...].
Score (0–1) how distinguishable two colors remain under each CVD.
Returns "#000000" or "#ffffff", whichever is more readable.
Find the palette stop with the highest passing contrast ratio.
Per-stop accessibility audit (best text color, AA pass for black/white).
Generate a coordinated light + dark theme from a single seed. The dark variant isn't an inverted ramp — it's recomposed with lower-chroma surfaces, lighter text roles, and a desaturated primary for comfort on emissive screens.
Returns a ThemePair with light, dark, lightAudit, and darkAudit.
const theme = generateTheme("#6366f1");
theme.light.primary; // "#3f37bb"
theme.dark.primary; // "#8b95ff"
theme.darkAudit; // [{ role, background, text, wcagRatio, apcaLc, passesAA }, ...]Each SemanticTheme contains: background, surface, surfaceMuted,
border, text, textMuted, textSubtle, primary, primaryForeground,
accent, accentForeground, success, warning, danger.
Emits :root { ... } plus a .dark { ... } override block.
Emits { seed, light, dark } JSON.
Derive classic color-harmony schemes from a single seed in OKLCH hue space, keeping the seed's lightness and chroma for a cohesive set.
Returns a Harmony with seed, scheme, and colors (each with role,
hue, offset, hex).
const triad = generateHarmony("#6366f1", "triadic");
triad.colors; // [{ role: "base", hex: "#6366f1" }, ...]Supported schemes: "complementary", "analogous", "triadic", "tetradic",
"split-complementary", "monochromatic".
Returns all six schemes in one call as a Record<HarmonyScheme, Harmony>.
Human-readable label and one-line description for a scheme.
chroma-flow ships three CIE color-difference metrics, from simplest to most perceptually accurate:
- ∆E76 — straight Euclidean distance in CIE Lab. Fast, but overestimates differences in saturated colors.
- ∆E94 — adds chroma/hue weighting (graphic-arts factors). A good middle ground.
- ∆E2000 (CIEDE2000) — the most perceptually accurate. Recommended default.
Each returns the ∆E for its method (0 = identical, ~2.3 = JND for ∆E2000).
deltaE76("#6366f1", "#5b5cf0"); // ~6.71 (overestimates)
deltaE94("#6366f1", "#5b5cf0"); // ~3.26
deltaE2000("#6366f1", "#5b5cf0"); // ~3.17 (most accurate)Dispatcher that selects the method. method ∈ "76" | "94" | "2000".
deltaE("#6366f1", "#5b5cf0"); // ∆E2000 (default)
deltaE("#6366f1", "#5b5cf0", "76"); // ∆E76Full ∆E2000 check with a human-readable band and a JND flag:
{
deltaE: 3.17,
band: "noticeable", // indistinguishable | barely noticeable | noticeable | clearly different | very different
belowJND: false, // true when deltaE < 2.3
}Formats for display: 3.17 → "∆E 3.17".
Find the closest color in a list using the requested ∆E method. Returns
{ hex, deltaE }.
nearestColor("#6366f1", ["#ef4444", "#3f37bb", "#10b981"]); // ∆E2000
nearestColor("#6366f1", ["#ef4444", "#3f37bb", "#10b981"], "76"); // ∆E76
// { hex: "#3f37bb", deltaE: 16.05 }OKLCH-based operations on individual colors. All return a hex string.
Blend two colors by factor t (0 = fully a, 1 = fully b), interpolating in
OKLCH with shortest-path hue.
mixColors("#6366f1", "#f59e0b", 0.5); // "#e95ea0"lighten(hex, amount = 0.1)— increase OKLCH lightness.darken(hex, amount = 0.1)— decrease OKLCH lightness.saturate(hex, amount = 0.05)— increase OKLCH chroma.desaturate(hex, amount = 0.05)— decrease OKLCH chroma.
rotateHue("#6366f1", 120)→#d93a00complementis shorthand forrotateHue(hex, 180).invertis channel-wise sRGB inversion (255 − channel).
Generate a random seed hex in OKLCH, constrained to a pleasant lightness/chroma range so the result is usually usable as a palette seed.
randomSeed(); // any vivid mid-tone
randomSeed({ hueRange: [140, 200] }); // a random teal/cyanRandomSeedOptions: minLightness, maxLightness, minChroma, maxChroma,
hueRange: [min, max].
Higher-level operations on full palettes.
Insert a midpoint between each standard stop. Returns a Record<string, string>
with 21 entries (11 stops + 10 midpoints), keyed by the interpolated numeric
label (e.g. "50", "75", "100", "125", …).
Reorder stops by OKLCH lightness or chroma, ascending or descending. order ∈
"lightness-asc" | "lightness-desc" | "chroma-asc" | "chroma-desc". Returns a
new Palette relabeled 50–950 in the new order.
Reverse the ramp so stop 50 holds what was 950, and vice versa. Returns a new
Palette.
Emit a CSS linear-gradient(...) string from the palette stops — handy for
previews.
The reverse of the export pipeline: parse an existing palette back into a chroma-flow seed, so you can reverse-engineer a design system and continue tweaking it with the generator.
Recognize --name-NNN: #hex; declarations and map the trailing number to a
palette stop. The palette name is the variable prefix before the stop number.
parseCSSPalette(`:root { --brand-500: #6366f1; --brand-600: #3f37bb; }`);
// { format: "css", name: "brand", colors: { 500: "#6366f1", 600: "#3f37bb" }, stops: [500, 600] }Recognize NNN: "#hex" entries inside a named color object in a Tailwind
config fragment.
Parse a { "500": "#hex", … } JSON object.
Auto-detect the format and parse. Falls back to a raw hex scan (assigning hexes to stops in order) if no structured format is recognized.
Find the seed color that best reproduces an imported palette. For each stop in the source, it treats that stop's hex as a candidate seed, generates a full palette, and measures the average ∆E2000 across shared stops. The candidate with the lowest average ∆E wins.
const { seed, fromStop, averageDeltaE, palette } = inferSeed(imported);
// { seed: "#6366f1", fromStop: 500, averageDeltaE: 0.187, palette: {…} }Convenience: parse a string and return both the imported palette and the inferred seed in one call.
Discover WCAG-conformant foreground/background pairs from a palette and audit every stop's text accessibility.
Search all stop-vs-stop combinations plus black/white text against each stop,
and return the pair with the highest contrast ratio that meets the requested
level. Returns null if no pair qualifies.
const pair = findAccessiblePair(palette, "AAA");
// { foreground: "#ffffff", background: "#0c033d", ratio: 19.16, apcaLc: -107, passesAA: true, passesAAA: true }options.includeBlackWhite (default true) controls whether black/white text
candidates are considered.
Suggest the most accessible text color from the palette for a given background stop, preferring palette colors over black/white when they meet the level with a higher ratio.
Audit every stop: report the black/white text ratios, the recommended text
color, the recommended pair's ratio, and a WCAG band
("AAA" | "AA" | "AA Large" | "Fail").
paletteAccessibilityMatrix(palette);
// [{ stop: 50, background: "#e8f5ff", blackRatio: 18.94, whiteRatio: 1.11,
// recommendedText: "#000000", recommendedRatio: 18.94, band: "AAA" }, ...]List the stop numbers that pass the requested WCAG level as a background (with either black or white text), in ascending order.
Checks the 3:1 contrast threshold for UI components and graphical objects per WCAG 2.2 SC 1.4.11 (Non-text Contrast). Applies to input boundaries, icons, focus indicators, chart segments, and other non-text content.
The WCAG 2.2 non-text contrast threshold: 3.
Check whether a foreground (UI component / graphic) meets the 3:1 threshold
against an adjacent background. kind is informational
("border" | "icon" | "focus-indicator" | "graphic" | "general").
checkNonTextContrast("#6366f1", "#ffffff", "border");
// { foreground: "#6366f1", background: "#ffffff", ratio: 4.47, passes: true, band: "Pass", kind: "border" }Boolean check for the 3:1 threshold.
Audit a UI component in one call: the fill against the page background, plus
(if a border is given) the border against both the page background (outside)
and the fill (inside). Returns an array of NonTextContrastResult.
Check a focus indicator's 3:1 contrast against its background (SC 1.4.13 / 1.4.11).
Audit every stop: report whether it meets 3:1 as a UI component color on the
given background. Returns { stop, color, ratio, passes }[] sorted by stop.
Convert between sRGB hex and the CSS color(display-p3 r g b) notation,
detect out-of-sRGB-gamut colors, and clamp wide-gamut colors back to sRGB.
Display-P3 is the gamut used by modern Apple displays and wide-gamut monitors.
Convert an sRGB hex color to a CSS color(display-p3 r g b) string (0–1).
toP3String("#10b981"); // "color(display-p3 0.090178 0.461936 0.251553)"Parse a CSS color(display-p3 r g b) string back to an sRGB hex. Values
outside the sRGB gamut are clamped.
Check whether a color falls inside the sRGB gamut. For hex, always true (hex
is inherently sRGB). For a color(display-p3 ...) string, returns false if
any channel is outside [0, 1] after conversion to sRGB.
Clamp a color(display-p3 ...) string to the sRGB gamut, returning a hex.
Full wide-gamut analysis: the P3 representation, gamut membership, and ∆E2000
gamut loss. Returns a GamutInfo object.
Compare theoretical OKLCH colors to their sRGB-clamped palette counterparts and report the ∆E2000 gamut loss per stop.
A single call that combines text contrast (WCAG 2.1), non-text contrast (WCAG 2.2 SC 1.4.11), and sRGB gamut loss into one per-stop table plus summary scores. The one-stop API for a complete palette accessibility audit.
Returns a FullAccessibilityReport with rows (per-stop), textPassing,
nonTextPassing, inGamut, total, and overallScore (0–1).
const report = fullAccessibilityReport(palette, "#ffffff", "AAA", { seed: "#6366f1" });
console.log(report.overallScore); // 0.82
console.log(report.textPassing); // 10options.seed is informational (included in the report for traceability).
A compact multi-line text summary suitable for CLI output or quick inspection.
console.log(summarizeReport(report));
// Palette accessibility report for seed #6366f1
// Background: #ffffff Level: WCAG AAA
// Text contrast: 10/11 (91%) pass
// Non-text (3:1): 6/11 (55%) pass
// In sRGB gamut: 11/11 (100%)
// Overall score: 82%Single entry point. format ∈ "css" | "tailwind" | "json" | "scss" | "svg" | "android-xml" | "swift" | "compose".
toCSS(palette, name?)— CSS custom properties.toCSSMulti(palettes)— multiple named palettes as CSS.toTailwind(palette, name)— Tailwind config fragment.toTailwindMulti(palettes)— multiple palettes as Tailwind config.toJSON(palette)— JSON object.toSCSS(palette, name?)— SCSS variables.toSVG(palette, name?)— SVG swatch sheet.toAndroidXML(palette, name?)— Androidcolors.xml.toSwift(palette, name?)— SwiftUIColorextension.toCompose(palette, name?)— Jetpack ComposeColorobject.
All types are exported from the package root: RGB, OKLCH, OKLab,
PaletteStop, Palette, GenerateOptions, WCAGLevel, ContrastResult,
CVDType, CVDPreview, ExportFormat, APHAResult, SemanticTheme,
ThemeAudit, ThemePair, HarmonyScheme, HarmonyColor, Harmony,
DeltaEResult, DeltaEMethod, RandomSeedOptions, SortOrder,
InferredSeed, ImportedPalette, AccessiblePair, PaletteAccessibilityRow,
NonTextContrastResult, GamutInfo, AccessibilityReportRow,
FullAccessibilityReport.