How the WCAG Contrast Ratio Checker Calculates Compliance in Design.md
The WCAG contrast ratio checker calculates compliance by comparing the relative luminance of text and background colors using the formula (L1 + 0.05) / (L2 + 0.05), automatically flagging any component with a ratio below the 4.5:1 WCAG AA minimum.
The contrast ratio checker is a built-in lint rule in the google-labs-code/design.md repository that enforces accessibility standards directly within your design system workflow. It operates as part of the Design.md CLI, parsing component definitions and validating color combinations against WCAG 2.1 guidelines. This ensures that text remains readable against its background before code ever reaches production.
How the Contrast Ratio Calculation Works
The checker follows a three-stage pipeline: resolving color tokens to luminance values, applying the WCAG contrast formula, and validating against the AA threshold.
Color Resolution and Luminance Pre-computation
First, the CLI parses the DESIGN.md file into a DesignSystemState. Each component's color properties—specifically backgroundColor and textColor—are resolved to ResolvedColor objects. These objects contain a pre-computed luminance value, a linearized lightness metric where 0 represents pure black and 1 represents pure white.
The WCAG Contrast Formula Implementation
The core calculation happens in packages/cli/src/linter/model/handler.ts. The contrastRatio function implements the official WCAG 2.1 algorithm:
// https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts#L400-L406
export function contrastRatio(a: ResolvedColor, b: ResolvedColor): number {
const L1 = Math.max(a.luminance, b.luminance);
const L2 = Math.min(a.luminance, b.luminance);
return (L1 + 0.05) / (L2 + 0.05);
}
This formula divides the lighter color's luminance plus 0.05 by the darker color's luminance plus 0.05, producing a ratio where 1:1 indicates identical colors and 21:1 indicates black-on-white.
Compliance Validation Against WCAG AA Standards
The contrastCheck rule in packages/cli/src/linter/rules/contrast-ratio.ts iterates through every component and compares the result against the WCAG_AA_MINIMUM constant of 4.5:
// https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/rules/contrast-ratio.ts#L19-L44
const WCAG_AA_MINIMUM = 4.5;
export function contrastCheck(state: DesignSystemState): RuleFinding[] {
const findings: RuleFinding[] = [];
for (const [compName, comp] of state.components) {
const bgValue = comp.properties.get('backgroundColor');
const textValue = comp.properties.get('textColor');
if (!bgValue || !textValue) continue;
const bgColor = resolveToColor(bgValue);
const textColor = resolveToColor(textValue);
if (!bgColor || !textColor) continue;
const ratio = contrastRatio(bgColor, textColor);
if (ratio < WCAG_AA_MINIMUM) {
findings.push({
path: `components.${compName}`,
message: `textColor (${textColor.hex}) on backgroundColor (${bgColor.hex}) has contrast ratio ${ratio.toFixed(2)}:1, below WCAG AA minimum of ${WCAG_AA_MINIMUM}:1.`,
});
}
}
return findings;
}
When the ratio falls below 4.5:1, the rule generates a RuleFinding warning that includes the specific hex values and computed ratio.
Running the Contrast Checker via CLI
Execute the lint command to validate your design system:
# Lint a DESIGN.md file and see contrast warnings
$ design-md lint path/to/DESIGN.md --format=text
Sample output shows detailed failure messages:
{
"findings": [
{
"path": "components.Button",
"message": "textColor (#777777) on backgroundColor (#FFFFFF) has contrast ratio 3.98:1, below WCAG AA minimum of 4.5:1."
}
],
"summary": { "warnings": 1, "errors": 0 }
}
Programmatic Usage of the Contrast Function
You can reuse the calculation in custom tooling:
import { contrastRatio } from '@design-md/cli';
import { resolveColor } from '@design-md/cli/model';
const bg = resolveColor('#ffffff'); // luminance ≈ 1.0
const txt = resolveColor('#777777'); // luminance ≈ 0.42
const ratio = contrastRatio(bg, txt);
console.log(`Contrast ratio: ${ratio.toFixed(2)}:1`); // → 3.98:1
Summary
- The WCAG contrast ratio checker operates as a lint rule within the Design.md CLI, validating accessibility at the design system level.
- It calculates ratios using the standard formula
(L1 + 0.05) / (L2 + 0.05)implemented inpackages/cli/src/linter/model/handler.ts. - The 4.5:1 minimum threshold for WCAG AA compliance is enforced by the
contrastCheckrule inpackages/cli/src/linter/rules/contrast-ratio.ts. - Color resolution happens automatically, handling token indirection and aliases through the
ResolvedColortype.
Frequently Asked Questions
What WCAG conformance level does the checker enforce?
The checker enforces WCAG 2.1 Level AA compliance, which requires a minimum contrast ratio of 4.5:1 for normal text. This threshold is hardcoded as the WCAG_AA_MINIMUM constant in the contrast ratio rule.
Does the checker support color tokens or only hex values?
The checker supports color tokens and aliases through automatic resolution. When processing a DESIGN.md file, the linter resolves all token references to ResolvedColor objects containing computed luminance values, ensuring that indirect color definitions are still validated against WCAG standards.
How does the linter handle missing background or text colors?
If a component lacks either backgroundColor or textColor properties, or if these properties cannot be resolved to valid colors, the contrastCheck rule skips that component silently using continue statements. Only components with both valid colors receive contrast validation.
Can I use the contrast calculation in my own lint rules?
Yes. The contrastRatio function is exported from packages/cli/src/linter/model/handler.ts and accepts two ResolvedColor objects. You can import it to build custom accessibility rules that check color combinations beyond the standard text-background pairs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →