Contrast Checker
Real-Time WCAG Contrast · Adaptive Borders · Passing Suggestions
Executive Summary
A real-time WCAG 2.1 AA/AAA contrast calculator with three features that go past a basic ratio readout: adaptive swatch borders (the border color adjusts to stay visible against both the foreground and background), passing suggestions (algorithmically generated alternatives that pass at the current text size), and shareable links (the color pair encoded in the URL). It's a single Flask route serving a self-contained vanilla-JS tool, with no API calls and pure client-side math. The insight: a contrast tool that only reports a ratio misses the most useful thing it could do, which is tell you what would pass.
The WCAG Contrast Problem
WCAG 2.1 requires 4.5:1 for normal text (AA), 3:1 for large text, and 7:1 for AAA. Most tools stop at reporting the ratio: pass or fail. The useful question is: if it fails, what's the nearest passing color? A secondary problem: swatch previews mislead. A fixed dark border around a white-on-white pair makes it look fine when it isn't. A tertiary one: contrast decisions happen in review, not isolation, and sharing a specific pair means copying hex values and describing the test.
Tool Architecture
Foreground and background hex inputs drive a relative-luminance calculation per the WCAG 2.1 formula on every keypress (3-digit shorthand expanded), with the ratio to two decimals and independent AA / AA-Large / AAA / AAA-Large badges; a large-text toggle shifts the threshold from 4.5:1 to 3:1. A swap button flips the pair instantly.
The swatch border color is computed from the pair (darkened when the foreground is light, lightened when it's dark) so the swatch boundary is always visible and never "disappears" into the page. The border never does the contrast work the foreground should.
When a pair fails, the tool walks the lightness channel (HSL) in both directions from the current foreground, testing each candidate against the background, and collects the first few that reach ≥4.5:1 (or 3:1 large). Each is shown as a chip with its hex and ratio, and one click applies it.
The URL encodes the pair as ?fg=533AFD&bg=FFFFFF; on load those params pre-fill the tool. It drops into Figma comments, Jira/Linear tickets, and Slack threads as a link, not a description.
Every evaluated pair is logged in session with a timestamp and grade, exportable as JSON. The
stack is a Flask route serving static HTML; all computation is client-side vanilla JS (no Anthropic
API, since contrast math is deterministic). httpx==0.27.0 stays pinned for the other CT Lab
tools.
Design Decisions
No API calls for the core function
Contrast is pure math: relative luminance, no ambiguity, a correct answer. An LLM would add latency, cost, and a failure mode to a computation that doesn't need one. The right tool for deterministic math is deterministic code.
Adaptive borders as a trust feature
A fixed border hides false signals; an adaptive one removes them. The border is always derived from the pair, never a constant that fakes legibility.
Suggestions as the primary action
The ratio is information; the suggestions are action. In review the question is rarely "does this pass?"; it's "if not, what do I change it to?" Generating passing alternatives makes the tool useful at the moment it matters.
URL encoding for async collaboration
Design decisions happen across tools and threads. A pair that can be shared as a link fits existing workflows without anyone installing anything.
What This Demonstrates
Accessibility precision (correct WCAG formula, all four thresholds, large-text handling), UX judgment (adaptive borders and suggestions solve real friction), and constraint-appropriate engineering (no API where deterministic code is the right answer). It completes a three-tool accessibility suite alongside the auditor and the pattern-level CVD simulation.