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.