Accessibility
Check WCAG contrast ratios, simulate color vision deficiencies, and audit palettes for color blindness safety.
Material
Text color
Color vision simulation
Emerald Tile
ET-4021
import { contrastRatio, meetsWCAG, simulateColorBlindnessHex } from "colorscope/accessibility";Contrast
contrastRatio
Calculate WCAG contrast ratio between two colors.
function contrastRatio(color1: ColorInput, color2: ColorInput): number| Param | Type | Description |
|---|---|---|
color1 | ColorInput | First color |
color2 | ColorInput | Second color |
Returns: Value from 1 (identical) to 21 (black vs white).
contrastRatio("#000000", "#ffffff"); // 21
contrastRatio("#ff6600", "#ffffff"); // ~3.0meetsWCAG
Check WCAG compliance for a color pair.
function meetsWCAG(color1: ColorInput, color2: ColorInput): WCAGResultReturns: Pass/fail for AA, AAA, and large text variants, plus the actual ratio.
const result = meetsWCAG("#1a1a1a", "#ffffff");
// { aa: true, aaa: true, aaLarge: true, aaaLarge: true, ratio: 17.58 }relativeLuminance
Calculate WCAG 2.x relative luminance from RGB.
function relativeLuminance(r: number, g: number, b: number): numberReturns: Value from 0 (black) to 1 (white).
suggestAccessibleForeground
Suggest the best foreground color for a given background.
function suggestAccessibleForeground(
background: ColorInput,
candidates?: readonly ColorInput[]
): { color: RGB; hex: string; ratio: number }| Param | Type | Default | Description |
|---|---|---|---|
background | ColorInput | — | Background color |
candidates | readonly ColorInput[] | [black, white] | Candidate foreground colors |
Tests each candidate and returns the one with the highest contrast ratio.
suggestAccessibleForeground("#1a1a1a");
// { color: { r: 255, g: 255, b: 255 }, hex: "#ffffff", ratio: 17.58 }
suggestAccessibleForeground("#ffcc00", ["#000000", "#ffffff", "#333333"]);
// { color: { r: 0, g: 0, b: 0 }, hex: "#000000", ratio: 15.95 }Color Vision Deficiency
simulateColorBlindness
Simulate how a color appears to someone with a color vision deficiency.
function simulateColorBlindness(
color: string | RGB | QuantizedColor,
type: ColorBlindnessType
): RGB| Param | Type | Description |
|---|---|---|
color | string | RGB | QuantizedColor | Input color |
type | ColorBlindnessType | Type of CVD to simulate |
Uses Viénot, Brettel & Mollon (1999) simulation matrices in linear RGB space. Achromatopsia uses luminance conversion.
simulateColorBlindnessHex
Simulate color blindness and return as hex string.
function simulateColorBlindnessHex(
color: string | RGB | QuantizedColor,
type: ColorBlindnessType
): stringsimulateColorBlindnessHex("#ff0000", "deuteranopia"); // "#9a8700"checkColorBlindSafety
Check if a palette is safe for people with color vision deficiencies.
function checkColorBlindSafety(
palette: readonly QuantizedColor[],
types?: readonly ColorBlindnessType[]
): ColorBlindSafetyReport[]| Param | Type | Default | Description |
|---|---|---|---|
palette | readonly QuantizedColor[] | — | Colors to check |
types | readonly ColorBlindnessType[] | All four types | Types to test |
Tests each pair of colors: if two colors are distinguishable in normal vision but become very similar when simulated, they're flagged as confusable.
Returns: Safety report for each CVD type.
const reports = checkColorBlindSafety(colors);
for (const report of reports) {
console.log(`${report.type}: ${report.safe ? "safe" : "has confusable pairs"}`);
}Types
WCAGResult
interface WCAGResult {
aa: boolean; // Passes AA for normal text (≥4.5:1)
aaa: boolean; // Passes AAA for normal text (≥7:1)
aaLarge: boolean; // Passes AA for large text (≥3:1)
aaaLarge: boolean; // Passes AAA for large text (≥4.5:1)
ratio: number; // The actual contrast ratio
}ColorInput
type ColorInput =
| string // hex string
| RGB // { r, g, b }
| { hue: number; saturation: number; lightness: number } // HSL-likeColorBlindnessType
type ColorBlindnessType = "protanopia" | "deuteranopia" | "tritanopia" | "achromatopsia"ColorBlindSafetyReport
interface ColorBlindSafetyReport {
type: ColorBlindnessType;
confusablePairs: Array<{
indexA: number;
indexB: number;
originalDistance: number;
simulatedDistance: number;
}>;
safe: boolean; // true if no confusable pairs
}