With the European Accessibility Act (EAA) and ADA Title II/III enforcement tightening across web applications, shipping illegible low-contrast text or invisible UI buttons is no longer just a UX mistake—it is a legal and compliance risk.

Even worse, modern SaaS apps often allow user-customizable theming (where tenants pick their own brand primary color) or rely on AI UI generators that invent hex codes on the fly.

When a customer picks #60A5FA (a light sky blue) as their button background with #FFFFFF white text, the contrast ratio drops to 2.54:1—failing WCAG 2.1 AA (4.5:1 required for normal text).

In this guide, we will look at how to programmatically audit color pairs against WCAG 2.1 and APCA (WCAG 3.0 draft), simulate color vision deficiencies, and automatically calculate the nearest accessible hex color in under 1 millisecond using Node.js.

Why Manual Contrast Checkers Don't Scale

Checking two colors manually in a browser extension works during static Figma handoffs, but fails in dynamic applications:

  1. Dynamic User Themes: When a SaaS user saves a custom brand color in settings, your backend needs to validate and adjust that color before saving it to the database—or generate a complete 50–950 shade scale with guaranteed accessible text pairings.
  2. Preserving Brand Hue During Auto-Fix: If #60A5FA fails against #FFFFFF, you cannot just replace the user's blue brand color with #000000. You need a mathematical binary search in HSL/OKLCH space that preserves their exact Hue and Saturation while shifting Lightness just enough to hit 4.5:1 (AA) or 7.0:1 (AAA).
  3. Color Blindness Verification: Two colors might pass luminance contrast on paper while looking virtually identical to users with Protanopia (red-blind) or Deuteranopia (green-blind).

Automating Accessibility & Auto-Fixing with ContrastGuard API

Instead of writing relative luminance gamma transfer functions, APCA perceptual polarity equations, and Brettel color-blindness matrices from scratch, we can query ContrastGuard API on RapidAPI.

It runs 100% in-memory with <1ms execution latency.

1. Auditing & Auto-Fixing a Failing Brand Color in Node.js

const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY || "YOUR_RAPIDAPI_KEY"; const CONTRASTGUARD_HOST = "contrastguard-wcag-color-contrast-and-accessibility-fixer.p.rapidapi.com"; async function auditAndFixColors(foreground, background, targetLevel = "AA") { const response = await fetch(`https://${CONTRASTGUARD_HOST}/v1/audit`, { method: "POST", headers: { "Content-Type": "application/json", "X-RapidAPI-Key": RAPIDAPI_KEY, "X-RapidAPI-Host": CONTRASTGUARD_HOST }, body: JSON.stringify({ foreground, background, targetLevel, fontSizePx: 16, fontWeight: 400 }) }); if (!response.ok) { throw new Error(`ContrastGuard HTTP error: ${response.status}`); } return response.json(); } async function runThemeCheck() { // Let's test a light blue (#60A5FA) on a white background (#FFFFFF) const result = await auditAndFixColors("#60A5FA", "#FFFFFF", "AA"); const { data, meta } = result; console.log(`Audited in ${meta.execution_time_ms} ms`); console.log(`Original Ratio: ${data.contrastRatio}:1 (Grade: ${data.wcag21.grade})`); if (!data.wcag21.normalTextAA && data.autoFix) { console.log(`❌ Failed WCAG AA! Auto-fixing while preserving brand hue...`); console.log(`✅ Nearest Accessible Foreground: ${data.autoFix.suggestedForegroundHex}`); console.log(`✅ New Verified Contrast Ratio: ${data.autoFix.fixedContrastRatio}:1`); } console.log(`Color Blindness (Deuteranopia) Preview:`, data.colorBlindnessSimulation.deuteranopia); } runThemeCheck(); 

2. Generating a Full 11-Step Accessible Tailwind Scale (POST /v1/palette)

When a user inputs a single primary hex code, you can call /v1/palette to generate an entire 50–950 design-system scale where every shade already includes its exact contrast ratio and recommended foreground text color (#FFFFFF vs #0F172A):

async function generateDesignSystemScale(baseColor) { const response = await fetch(`https://${CONTRASTGUARD_HOST}/v1/palette`, { method: "POST", headers: { "Content-Type": "application/json", "X-RapidAPI-Key": RAPIDAPI_KEY, "X-RapidAPI-Host": CONTRASTGUARD_HOST }, body: JSON.stringify({ baseColor, background: "#FFFFFF" }) }); const { data } = await response.json(); console.table(data.scale || data.shades); } generateDesignSystemScale("#3B82F6"); 

3. Auditing an Entire CSS Theme in One Request (POST /v1/batch)

If you want to add an automated accessibility gate in your CI/CD pipeline or design token build step, pass up to 50 foreground/background pairs at once to POST /v1/batch:

const batchCheck = await fetch(`https://${CONTRASTGUARD_HOST}/v1/batch`, { method: "POST", headers: { "Content-Type": "application/json", "X-RapidAPI-Key": RAPIDAPI_KEY, "X-RapidAPI-Host": CONTRASTGUARD_HOST }, body: JSON.stringify({ pairs: [ { id: "btn-primary", foreground: "#FFFFFF", background: "#2563EB", targetLevel: "AA" }, { id: "badge-warning", foreground: "#F59E0B", background: "#FFFFFF", targetLevel: "AA" }, { id: "footer-muted", foreground: "#64748B", background: "#0F172A", targetLevel: "AA" } ] }) }); 

Try ContrastGuard API

How do you enforce WCAG contrast rules across custom user themes or design systems? Let me know in the comments!