How the Contrast-Ratio Rule Ensures WCAG Accessibility Compliance in design.md

The contrast-ratio rule automatically validates that every component meets the WCAG 2.1 AA standard by requiring a minimum 4.5:1 contrast ratio between text and background colors, flagging violations as linter warnings.

The google-labs-code/design.md repository provides a CLI linter that enforces accessibility standards across design system components without manual review. The contrast-ratio rule specifically targets WCAG compliance by analyzing backgroundColor and textColor properties defined in DESIGN.md files, ensuring text remains readable for users with visual impairments.

How the Contrast-Ratio Rule Works

The rule implements a three-step pipeline that runs on every component definition. According to the source code in packages/cli/src/linter/linter/rules/contrast-ratio.ts, the process resolves color tokens, calculates luminance-based contrast, and compares results against the WCAG AA threshold.

Step 1: Resolve Token Values

For each component, the rule extracts backgroundColor and textColor properties, dereferences any token references (such as {colors.primary}), and converts them into concrete ResolvedColor objects. The resolveToColor helper ensures only valid color objects proceed to calculation.

This resolution happens in contrast-ratio.ts lines 47-52, where the linter handles both direct hex values and token references defined in the DESIGN.md file.

Step 2: Compute WCAG Contrast

The linter calls the contrastRatio utility function defined in packages/cli/src/linter/model/handler.ts (lines 400-406). This pure function applies the official WCAG luminance formula:

(L1 + 0.05) / (L2 + 0.05)

The calculation uses relative luminance values stored on each ResolvedColor object, comparing the lighter color (L1) against the darker color (L2) to produce a ratio that indicates how distinguishable the colors are.

Step 3: Compare Against AA Threshold

The rule enforces the constant WCAG_AA_MINIMUM = 4.5, which mirrors the WCAG 2.1 AA requirement for normal-sized text. In contrast-ratio.ts lines 36-42, the linter checks:

if (ratio < WCAG_AA_MINIMUM) {
  // Create RuleFinding with offending colors and actual ratio
}

When the computed ratio falls below 4.5, the rule emits a warning RuleFinding that includes the specific color values, the calculated contrast ratio, and a clear failure message.

Implementation Details

The contrast-ratio rule relies on two primary source files within the repository:

The rule is documented in the README.md under "Linting Rules," confirming its severity level and description for end-users.

Practical Examples

Command Line Usage

Run the linter against any DESIGN.md file to check for contrast violations:

npx @google/design.md lint my-design.md --format markdown

When a component violates the standard, the output displays the specific violation:


components.Button:
  textColor (#ffffff) on backgroundColor (#e0e0e0) has contrast ratio 2.87:1,
  below WCAG AA minimum of 4.5:1.

Programmatic API Integration

You can invoke the contrast-ratio rule directly through the TypeScript API:

import { lint } from '@google/design.md/linter';

const designMarkdown = `
components:
  Card:
    backgroundColor: "#f3f3f3"
    textColor: "#777777"
`;

const report = lint(designMarkdown);

for (const finding of report.findings) {
  if (finding.message.includes('contrast ratio')) {
    console.log(`⚠️ ${finding.path}: ${finding.message}`);
  }
}

This approach returns the same warning data structure as the CLI, allowing you to integrate contrast checks into CI/CD pipelines or custom build tools.

DESIGN.md Configuration That Triggers Warnings

The following YAML structure demonstrates a common token resolution scenario that fails compliance:

components:
  Alert:
    backgroundColor: "{colors.primary}"
    textColor: "#ffffff"
colors:
  primary: "#c0c0c0"

When linted, the rule resolves {colors.primary} to #c0c0c0, computes a contrast ratio of approximately 1.79:1 against the white text, and reports a warning because this falls well below the 4.5:1 minimum.

Summary

  • The contrast-ratio rule enforces WCAG 2.1 AA compliance by requiring a minimum 4.5:1 contrast ratio for all component text and background combinations.
  • The implementation uses a three-step pipeline: token resolution in contrast-ratio.ts, luminance calculation in handler.ts, and threshold comparison against the WCAG_AA_MINIMUM constant.
  • Violations generate automated warnings that include specific color values and calculated ratios, enabling rapid identification of accessibility issues.
  • The rule supports both CLI execution and programmatic API usage, making it suitable for integration into design system workflows and continuous integration pipelines.

Frequently Asked Questions

What WCAG standard does the contrast-ratio rule enforce?

The rule enforces the WCAG 2.1 Level AA standard, which requires a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text (18pt or 14pt bold). The implementation uses the constant WCAG_AA_MINIMUM = 4.5 defined in the source code to validate compliance automatically.

How does the rule handle design tokens and color references?

The rule uses the resolveToColor helper function to dereference token values like {colors.primary} before performing calculations. This ensures that the contrast evaluation works against the actual resolved color values rather than token names, providing accurate accessibility checking even when colors are defined centrally in the DESIGN.md file.

What happens when a component fails the contrast check?

When the calculated ratio falls below 4.5, the linter creates a RuleFinding object containing the specific component path, the offending background and text colors, the actual calculated ratio, and a descriptive message. This finding appears in the linter report as a warning and can be output in various formats including Markdown and JSON.

Can I use the contrast-ratio rule outside of the CLI?

Yes. The rule is fully accessible through the programmatic API by importing the lint function from @google/design.md/linter. When you pass a DESIGN.md string to this function, it processes all rules including contrast-ratio, returning a report object containing any findings. This allows integration into custom testing frameworks, pre-commit hooks, or build processes.

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 →