weableColor Icon
OverviewPricingDocsToolsBlogCase Studies
Get Started
Back to Blog

Tutorial: Integrating Accessibility Checks into Your CI/CD Pipeline

7/27/2025

accessibility
ci/cd
automation
testing
github actions
node.js
jest
vitest
api
@weable/a11y-color-utils

Tutorial: Integrating Accessibility Checks into Your CI/CD Pipeline

Manually checking color contrast is crucial during design and development, but automating these checks in your Continuous Integration/Continuous Deployment (CI/CD) pipeline provides a safety net, preventing inaccessible color combinations from reaching production.

This tutorial explores two ways to integrate automated contrast checks using @weable/a11y-color-utils into a typical CI/CD workflow, focusing on GitHub Actions as an example.

Prerequisites

  • A project using Node.js and npm/yarn.
  • Basic understanding of CI/CD concepts and GitHub Actions.
  • Familiarity with a testing framework like Jest or Vitest (for Option 1).
  • The API server from the previous tutorial running (accessible to the CI runner) or deployed (for Option 2).

Why Automate Accessibility Checks?

  • Consistency: Ensures standards are checked on every commit/PR.
  • Prevention: Catches regressions or newly introduced issues early.
  • Efficiency: Frees up developers from repetitive manual checks.
  • Compliance: Helps maintain adherence to WCAG standards required by law.

Option 1: Direct Library Integration in Tests (Jest/Vitest)

This approach involves writing unit or integration tests that use @weable/a11y-color-utils directly to verify critical color combinations within your application, often defined in theme files or design system constants.

1. Install Dependencies:

npm install --save-dev jest @weable/a11y-color-utils
# or
npm install --save-dev vitest @weable/a11y-color-utils

2. Create Test File (e.g., tests/accessibility.test.js):

// tests/accessibility.test.js
import {
  getColorContrast,
  getColorContrastAPCA,
  hexToRgb,
} from '@weable/a11y-color-utils';

// --- Configuration ---
// Ideally, import these from your theme/design system constants
const themeColors = {
  primaryText: '#212121',
  primaryBackground: '#FFFFFF',
  accent: '#3498DB',
  accentText: '#FFFFFF',
  error: '#C0392B',
  errorText: '#FFFFFF',
  subtleText: '#777777',
  // ... add other critical colors
};

const WCAG_AA_NORMAL_THRESHOLD = 4.5;
// Example APCA Threshold (Body Text: 16px/400 weight)
const APCA_BODY_THRESHOLD = 75; 

// --- Helper Function ---
function checkContrastPair(label, colorHex1, colorHex2) {
  const color1 = hexToRgb(colorHex1);
  const color2 = hexToRgb(colorHex2);

  if (!color1 || !color2) {
    throw new Error(`Invalid HEX color in pair: ${label}`);
  }

  const wcagRatio = getColorContrast(color1, color2);
  const apcaLc = Math.abs(getColorContrastAPCA(color1, color2)); // Use absolute value

  // WCAG Check (Minimum AA)
  expect(wcagRatio).toBeGreaterThanOrEqual(WCAG_AA_NORMAL_THRESHOLD);

  // Optional: APCA Check (Example: Body text readability)
  // Note: Adjust threshold based on expected usage (font size/weight)
  // expect(apcaLc).toBeGreaterThanOrEqual(APCA_BODY_THRESHOLD);
  
  console.log(`Checked: ${label} - WCAG: ${wcagRatio.toFixed(2)}, APCA Lc: ${apcaLc.toFixed(1)}`);
}

// --- Test Suite ---
describe('Color Contrast Accessibility Checks', () => {
  test('Primary Text on Primary Background meets WCAG AA', () => {
    checkContrastPair(
      'Primary Text/Background',
      themeColors.primaryText,
      themeColors.primaryBackground
    );
  });

  test('Accent Text on Accent Background meets WCAG AA', () => {
    checkContrastPair(
      'Accent Text/Background',
      themeColors.accentText,
      themeColors.accent
    );
  });

  test('Error Text on Error Background meets WCAG AA', () => {
    checkContrastPair(
      'Error Text/Background',
      themeColors.errorText,
      themeColors.error
    );
  });

  test('Subtle Text on Primary Background meets WCAG AA', () => {
     // This might intentionally fail if 'subtle' doesn't meet AA
     // Adjust threshold or colors based on design requirements
    checkContrastPair(
      'Subtle Text/Background',
      themeColors.subtleText,
      themeColors.primaryBackground
    );
  });

  // ... Add more tests for critical combinations
});

3. Configure package.json:

{
  "scripts": {
    "test": "jest",
    // or
    // "test": "vitest run"
    "test:ci": "npm run test -- --ci --silent" // Example for CI environment
  }
}

4. GitHub Actions Workflow (.github/workflows/ci.yml):

name: Node.js CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
    - uses: actions/checkout@v4
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - name: Install Dependencies
      run: npm ci
    - name: Run Accessibility Tests
      run: npm run test:ci # Run tests, including contrast checks
      # The job will fail if any expect() fails

Option 2: Calling the Accessibility Check API

If you have deployed the API server from the previous tutorial, you can call it from your CI script.

1. Define Critical Pairs (e.g., in a script or file):

You might have a simple script or configuration file defining the color pairs you want to check.

2. GitHub Actions Workflow (.github/workflows/ci.yml):

name: Node.js CI & API Checks

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  accessibility_check:
    runs-on: ubuntu-latest
    # Ensure API_URL is configured (e.g., via GitHub Secrets)
    env:
      API_URL: ${{ secrets.A11Y_CHECK_API_URL }} # e.g., https://your-a11y-api.com/check-contrast

    steps:
    - uses: actions/checkout@v4
    
    # Example: Check a single pair directly with curl
    - name: Check Primary Text/Background via API
      run: |
        response=$(curl -s -w "%{http_code}" -X POST "$API_URL" \
          -H "Content-Type: application/json" \
          -d '{
                "textColor": "#212121", 
                "backgroundColor": "#FFFFFF"
              }')
        http_code=$(tail -n1 <<< "$response") # Extract HTTP code
        body=$(sed '$d' <<< "$response")       # Extract body

        if [ "$http_code" -ne 200 ]; then
          echo "API Error: HTTP $http_code"
          echo "$body"
          exit 1
        fi

        # Check WCAG ratio (using jq to parse JSON)
        wcag_ratio=$(echo "$body" | jq '.results.wcag.ratio')
        wcag_pass=$(echo "$wcag_ratio >= 4.5" | bc -l)
        
        echo "Checked Primary: WCAG Ratio $wcag_ratio"
        if [ "$wcag_pass" -ne 1 ]; then
          echo "Accessibility Check Failed: Primary Text/Background WCAG ratio ($wcag_ratio) is below 4.5"
          exit 1
        else
          echo "Accessibility Check Passed: Primary Text/Background"
        fi

    # --- Add more checks for other pairs ---
    # You would likely use a script here to loop through multiple pairs
    # - name: Check Multiple Pairs via API Script
    #   run: node ./scripts/check-colors-via-api.js 

(Note: The curl example is basic. A dedicated script (check-colors-via-api.js) using Node.js fetch and handling multiple pairs would be more robust.)

Failing the Build

Both approaches naturally integrate with CI:

  • Option 1: If any expect() assertion in your Jest/Vitest tests fails, the test runner exits with a non-zero code, failing the GitHub Actions step.
  • Option 2: The script calling the API (like the curl example or a Node.js script) must explicitly check the results and exit 1 if a contrast check fails. This non-zero exit code fails the GitHub Actions step.

Next Steps

  • Expand Coverage: Add more critical color pairs to your tests or API checks.
  • Refine Thresholds: Adjust WCAG (e.g., AAA) or APCA thresholds based on specific component requirements.
  • Integrate APCA: Add APCA checks (using appropriate Lc thresholds based on font context) alongside WCAG.
  • Linting: Explore linters (like stylelint-a11y) that can catch some issues statically.
  • Reporting: Configure test runners or scripts to produce clearer reports on failures.

By integrating automated accessibility checks into your CI/CD pipeline using @weable/a11y-color-utils (either directly or via an API), you build a more robust process for creating and maintaining accessible web applications.

weableColor Icon
weableColor

Making accessibility sexy, one color at a time. Professional tools for developers who care about inclusive design.

WCAG 2.1 AA
APCA Ready
Products

© 2025 weableColor. All rights reserved. Made with ❤️ for accessible design.

Built with:

TypeScript
SACA