How the contrast-ratio Rule Checks WCAG Compliance in DESIGN.md

The contrast-ratio rule enforces WCAG AA compliance by calculating the luminance ratio between every component's backgroundColor and textColor, flagging any pair that falls below the 4.5:1 minimum threshold.

The contrast-ratio rule in the DESIGN.md CLI linter ensures design systems meet accessibility standards by automatically validating color contrast ratios. According to the google-labs-code/design.md source code, this rule inspects resolved design tokens and applies the WCAG 2.1 luminance formula to guarantee text remains readable against its background.

Rule Architecture and Entry Point

The rule is exported from packages/cli/src/linter/linter/rules/contrast-ratio.ts as contrastCheckRule. The run function (lines 54‑58) serves as the entry point, delegating execution to the contrastCheck function.

This architecture separates the rule declaration from the validation logic, allowing the linter engine to register the rule while the implementation handles the specific WCAG compliance checks.

Processing Components and Resolving Colors

The contrastCheck function iterates through every component defined in the resolved DesignSystemState (state.components). For each component, it extracts the backgroundColor and textColor properties (lines 27‑30).

If either property is missing, the component is skipped. Otherwise, the raw design-token values are converted into ResolvedColor objects using the resolveToColor helper (lines 47‑51). This resolution step ensures that token references like {colors.brand} are evaluated to their actual hex or RGB values before contrast calculation begins.

WCAG Contrast Ratio Calculation

The actual mathematical validation happens in packages/cli/src/linter/model/handler.ts. The contrastRatio function implements the standard WCAG 2.1 luminance contrast algorithm:

const L1 = Math.max(a.luminance, b.luminance);
const L2 = Math.min(a.luminance, b.luminance);
return (L1 + 0.05) / (L2 + 0.05);

(See handler.ts lines 100‑106.)

This formula calculates the relative luminance of both colors, then divides the lighter value by the darker one (after adding 0.05 to each to avoid division by zero). The result represents the contrast ratio between the two colors.

Threshold Enforcement and Violation Reporting

The computed ratio is compared against the constant WCAG_AA_MINIMUM = 4.5 (defined in contrast-ratio.ts line 19). When a ratio falls below this threshold, the rule generates a warning RuleFinding (lines 36‑42).

Each warning includes:

  • The hex values of both colors
  • The actual calculated ratio (rounded to two decimal places)
  • The required 4.5:1 minimum

The function returns the complete list of findings, which the linter reports as warnings for any non-compliant component.

Practical Implementation Example

The following example demonstrates how the rule validates a design system definition:

import { resolveDesign } from '@design-md/cli';
import { lint } from '@design-md/cli';

// Example design snippet (YAML)
const design = `
colors:
  brand: '#0044cc'
  onBrand: '#ffffff'

components:
  button:
    backgroundColor: '{colors.brand}'
    textColor: '{colors.onBrand}'
`;

const state = resolveDesign(design);
const findings = lint(state, { rules: ['contrast-ratio'] });

if (findings.length) {
  console.log('Contrast issues found:');
  findings.forEach(f => console.log(f.message));
}

Running this code prints a warning only when the contrast ratio is less than 4.5:1, ensuring the design system maintains WCAG AA compliance before deployment.

Summary

  • The contrast-ratio rule lives in packages/cli/src/linter/linter/rules/contrast-ratio.ts and exports contrastCheckRule.
  • It validates every component's backgroundColor/textColor pair against the WCAG AA minimum of 4.5:1.
  • Colors are resolved to ResolvedColor objects using resolveToColor before calculation.
  • The contrastRatio function in handler.ts implements the WCAG 2.1 luminance formula: (L1 + 0.05) / (L2 + 0.05).
  • Violations generate detailed warnings containing actual and required contrast values.

Frequently Asked Questions

What WCAG standard does the contrast-ratio rule enforce?

The rule enforces WCAG 2.1 Level AA compliance, specifically the minimum contrast ratio requirement of 4.5:1 for normal text. This threshold is defined as the constant WCAG_AA_MINIMUM in the source code.

How does the rule handle design tokens that reference other variables?

The rule uses the resolveToColor helper function to resolve token references (like {colors.brand}) to their actual color values before calculating contrast. This ensures the validation works against the final resolved color values rather than the raw token strings.

What happens when a component has only one color defined?

If a component is missing either backgroundColor or textColor, the rule skips that component entirely (lines 27‑30). The contrast check only runs when both color properties are present in the component definition.

Where is the contrast calculation formula implemented?

The mathematical implementation resides in packages/cli/src/linter/model/handler.ts within the contrastRatio function (lines 100‑106). This function calculates relative luminance using the standard WCAG formula (L1 + 0.05) / (L2 + 0.05) and is imported by the contrast-ratio rule for validation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →