Skip to content

A simple script that helps you get colors compliant with the WCAG guidelines.

Notifications You must be signed in to change notification settings

ProgressPlanner/wcagColors.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

wcagColors.js

wcagColors.js is a dependency-free ESM utility for generating, filtering, sorting, and converting colors with WCAG-focused contrast workflows.

It is designed for fast color candidate exploration in apps, design tooling, and accessibility checks.

What You Get

  • Pure functions (no internal mutable singleton state)
  • Strict color parsing with clear errors for unsupported formats
  • End-to-end color pipelines: generate -> filter -> sort -> output
  • WCAG relative luminance and contrast helpers
  • Lightweight conversion helpers (RGB, HSL, HEX)

Requirements

  • JavaScript runtime with ESM support
  • Node.js 18+ recommended

Installation

This repository ships a single module entrypoint.

import {
	parseColor,
	generateColors,
	filterColors,
	sortColors,
	toHexArray
} from "./wcagColors.js";

Quick Start

import {
	generateColors,
	filterColors,
	sortColors,
	toHexArray
} from "./wcagColors.js";

const pool = generateColors({
	color: "#0000ff",
	minHueDiff: 0,
	maxHueDiff: 10,
	stepHue: 5,
	minSaturation: 0.4,
	maxSaturation: 1,
	stepSaturation: 0.05,
	minLightness: 0,
	maxLightness: 1,
	stepLightness: 0.05
});

const readableOnWhite = filterColors(pool, {
	color: "#ffffff",
	minContrast: 7
});

const readableOnWhiteAndText = filterColors(readableOnWhite, {
	color: "#000000",
	minContrast: 3
});

const result = toHexArray(sortColors(readableOnWhiteAndText, "s", "asc"));
console.log(result);

Core Data Shape

Most APIs work with this normalized color object:

{
	r: 51,
	g: 102,
	b: 153,
	h: 210,
	s: 0.5,
	l: 0.4,
	hex: "#336699",
	lum: 0.1250645743,
	contrast: 4.93 // optional, added by filterColors when contrast filtering is used
}

API Reference

parseColor(input)

Parses and normalizes color input into a full color object.

Supported input formats:

  • HEX strings: #rgb, #rrggbb
  • RGB strings: rgb(r,g,b), rgba(r,g,b,a)
  • HSL strings: hsl(h,s%,l%), hsla(h,s%,l%,a)
  • RGB objects: { r, g, b }
  • HSL objects: { h, s, l }
  • Objects with a hex property
import { parseColor } from "./wcagColors.js";

parseColor("#fff");
parseColor("rgb(51, 102, 153)");
parseColor("hsl(210, 50%, 40%)");
parseColor({ r: 51, g: 102, b: 153 });
parseColor({ h: 210, s: 0.5, l: 0.4 });

Throws:

  • Error if input type or format is unsupported

generateColors(options)

Generates deduplicated candidate colors from hue, saturation, and lightness ranges.

Parameters:

  • color (string | object, optional): base color used to infer hue if hue is not provided
  • hue (number, optional): base hue in degrees
  • minSaturation (number, default 0)
  • maxSaturation (number, default 1)
  • stepSaturation (number, default 0.1, must be > 0)
  • minLightness (number, default 0)
  • maxLightness (number, default 1)
  • stepLightness (number, default 0.1, must be > 0)
  • minHueDiff (number, default 0)
  • maxHueDiff (number, default 360)
  • stepHue (number, default 15, must be > 0)
import { generateColors } from "./wcagColors.js";

const colors = generateColors({
	hue: 220,
	minHueDiff: 0,
	maxHueDiff: 20,
	stepHue: 5,
	minSaturation: 0.2,
	maxSaturation: 0.9,
	stepSaturation: 0.05,
	minLightness: 0.2,
	maxLightness: 0.8,
	stepLightness: 0.05
});

Returns:

  • ParsedColor[]

Throws:

  • Error if neither hue nor valid color is provided
  • Error if a step value is zero or negative

filterColors(colors, criteria)

Filters candidate colors by minimum contrast and/or hue difference from a reference color.

Parameters:

  • colors (ParsedColor[]): source candidates
  • criteria.color (string | object, required when using hue or contrast filters)
  • criteria.minContrast (number, optional)
  • criteria.minHueDiff (number, optional)
  • criteria.maxHueDiff (number, optional)
import { filterColors } from "./wcagColors.js";

const filtered = filterColors(colors, {
	color: "#ffffff",
	minContrast: 4.5,
	minHueDiff: 10,
	maxHueDiff: 60
});

Notes:

  • If no filtering keys are provided, it returns a shallow copy of the input array.
  • If minContrast is used, each returned item includes a computed contrast value.
  • Hue difference is circular (for example, 5 degrees and 355 degrees are 10 degrees apart).

sortColors(colors, sortBy, direction = "desc")

Sorts colors without mutating the input array.

Parameters:

  • colors (ParsedColor[])
  • sortBy (string): common keys include r, g, b, h, s, l, lum, contrast, hex
  • direction ("asc" | "desc", default "desc")
import { sortColors } from "./wcagColors.js";

const byContrast = sortColors(filtered, "contrast", "desc");
const bySaturation = sortColors(filtered, "s", "asc");

toHexArray(colors)

Maps normalized color objects to a plain hex string array.

import { toHexArray } from "./wcagColors.js";

const hexes = toHexArray(filtered);

Utility Functions

rgbToHex(r, g, b)

  • Converts RGB channels to normalized 6-digit hex

hexToRgb(hex)

  • Converts #rgb or #rrggbb to { r, g, b }

hslToRgb(h, s, l)

  • Converts HSL to RGB

rgbToHsl(r, g, b)

  • Converts RGB to HSL

getRelativeLuminance({ r, g, b })

  • Computes WCAG relative luminance

getContrast(lumA, lumB)

  • Computes WCAG contrast ratio between two luminance values

Practical Recipes

Find body text on a custom background (AA)

import { generateColors, filterColors, sortColors } from "./wcagColors.js";

const base = "#f4f1ea";
const candidates = generateColors({
	color: base,
	minHueDiff: 120,
	maxHueDiff: 240,
	stepHue: 10,
	minSaturation: 0,
	maxSaturation: 0.4,
	stepSaturation: 0.05,
	minLightness: 0,
	maxLightness: 0.7,
	stepLightness: 0.05
});

const readable = filterColors(candidates, { color: base, minContrast: 4.5 });
const best = sortColors(readable, "contrast", "desc")[0];

Find link colors against background and surrounding text

import { generateColors, filterColors, sortColors, toHexArray } from "./wcagColors.js";

const background = "#ffffff";
const text = "#222222";

const pool = generateColors({
	hue: 210,
	minHueDiff: 0,
	maxHueDiff: 20,
	stepHue: 2,
	minSaturation: 0.3,
	maxSaturation: 1,
	stepSaturation: 0.02,
	minLightness: 0,
	maxLightness: 0.9,
	stepLightness: 0.02
});

const onBackground = filterColors(pool, { color: background, minContrast: 4.5 });
const distinctFromText = filterColors(onBackground, { color: text, minContrast: 3 });

const ranked = sortColors(distinctFromText, "contrast", "desc");
const hexes = toHexArray(ranked);

Error Handling Guidance

  • Wrap user-provided color parsing in try/catch.
  • Validate user sliders or form input before calling generateColors.
  • Keep step values greater than zero.
import { parseColor } from "./wcagColors.js";

try {
	const color = parseColor(userInput);
	console.log(color);
} catch (error) {
	console.error("Invalid color input", error.message);
}

Performance Notes

  • Smaller steps produce larger candidate pools.
  • Start with coarse steps (0.1, 10) and refine only where needed.
  • Apply restrictive filters early to reduce sort cost.
  • Use benchmarks in this repo to tune your workload.

Run local checks:

npm test
npm run bench

License

MIT

About

A simple script that helps you get colors compliant with the WCAG guidelines.

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published