Skip to content

Color Contrast Checker — JavaScript source

Check WCAG 2.2 contrast ratio between any two colors with AA / AAA pass-fail for normal and large text, plus a live preview. For accessible, on-brand design.

This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

/**
 * Color contrast - WCAG 2.2 color contrast math.
 * Language: JavaScript (ES module).
 *
 * CosmoDev polyglot showcase port of the `contrast` tool.
 * Ported from src/lib/color.ts - functionally equivalent (same inputs -> same outputs).
 *
 * Display source - part of CosmoDev's polyglot tool pages.
 */

/**
 * Parses a CSS-style hex color into an [r, g, b] triple (each 0-255).
 *
 * Accepts an optional leading '#', a 3-digit shorthand ("#abc"), or the
 * 6-digit form ("#aabbcc"). Returns null when the string is not a valid
 * hex color so callers can distinguish "invalid" from a real color.
 *
 * @param {string} hex
 * @returns {[number, number, number] | null}
 */
export function hexToRgb(hex) {
  let h = hex.trim().replace(/^#/, '');
  // Expand CSS shorthand: each hex digit is doubled ("abc" -> "aabbcc").
  if (h.length === 3) {
    h = h.split('').map((c) => c + c).join('');
  }
  if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
  return [
    parseInt(h.slice(0, 2), 16),
    parseInt(h.slice(2, 4), 16),
    parseInt(h.slice(4, 6), 16),
  ];
}

/**
 * Linearizes a single sRGB channel value (0-255) per WCAG 2.2.
 *
 * 8-bit color values are gamma-encoded for display; WCAG luminance must be
 * computed in linear-light space, so each channel is run through the inverse
 * sRGB transfer function. The small-value branch is the linear segment of
 * that curve.
 *
 * @param {number} c
 * @returns {number}
 */
function channel(c) {
  c /= 255;
  return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}

/**
 * WCAG relative luminance of a hex color, on a 0..1 scale.
 * Returns null if the hex string is invalid.
 *
 * @param {string} hex
 * @returns {number | null}
 */
export function luminance(hex) {
  const rgb = hexToRgb(hex);
  if (!rgb) return null;
  // Rec. 709 luma coefficients for the red/green/blue primaries.
  return 0.2126 * channel(rgb[0]) + 0.7152 * channel(rgb[1]) + 0.0722 * channel(rgb[2]);
}

/**
 * WCAG contrast ratio between two hex colors, on a 1..21 scale.
 * Returns null if either color is invalid.
 *
 * The 0.05 offset models the ambient luminance assumed by WCAG so that the
 * ratio stays finite (and >= 1) even for identical colors.
 *
 * @param {string} fg
 * @param {string} bg
 * @returns {number | null}
 */
export function contrastRatio(fg, bg) {
  const l1 = luminance(fg);
  const l2 = luminance(bg);
  if (l1 == null || l2 == null) return null;
  return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →