Integration · Setup

Contrast in CI Fail the pull request, and say what to change

A small script sends each of your color pairs to the API, fails the job when one falls short, and names the nearest text color that passes. The output on this page is the script’s own, from running it against the API’s server.

Needs
An API key on the Specialist plan; Node.js 18 or later
Cost
One credit a pair, three for a pair that fails
Checked

Before you start

What you need

  • An API key from your dashboard, on the Specialist plan. Apprentice and Artisan plans have no API access.
  • A repository on GitHub, or any CI that runs Node.js 18 or later. The script was run on Node.js 24.11.0.
  • Your color pairs: which text sits on which background, and the ratio it needs.

Setup

Step by step

  1. Write your pairs

    One entry for each pair that must stay readable: text on its background at 4.5:1, large text and the edges of controls and icons at 3:1. Name each pair the way your team does.

    contrast-pairs.json

    [
      { "name": "Body text", "fg": "#1F2937", "bg": "#FFFFFF", "min": 4.5 },
      { "name": "Muted text", "fg": "#9CA3AF", "bg": "#FFFFFF", "min": 4.5 },
      { "name": "Button label", "fg": "#FFFFFF", "bg": "#4338CA", "min": 4.5 },
      { "name": "Input border", "fg": "#6B7280", "bg": "#FFFFFF", "min": 3 }
    ]
    

    Download contrast-pairs.json

  2. Add the script

    Put check-contrast.mjs at the root of the repository. It needs nothing but Node.js: no packages and no lockfile.

    Download check-contrast.mjs

    Try it on your own machine first

    # macOS and Linux
    AURIC_API_KEY="aa_your_key" node check-contrast.mjs contrast-pairs.json
    
    # Windows PowerShell
    $env:AURIC_API_KEY = "aa_your_key"; node check-contrast.mjs contrast-pairs.json
  3. Store the key as a secret

    In the repository’s settings, add a secret named AURIC_API_KEY holding your key. The workflow passes it in, and the script never prints it.

  4. Add the workflow

    Save it as .github/workflows/contrast.yml. It runs on every pull request, and from the Actions tab whenever you ask.

    contrast.yml

    # Check color contrast on every pull request.
    # From https://auricartisan.com/integrations/contrast-ci/ - CC0 1.0.
    # Put this file in .github/workflows/, check-contrast.mjs and contrast-pairs.json
    # at the root of the repository, and an API key in the repository secret AURIC_API_KEY.
    name: Contrast
    
    on:
      pull_request:
      workflow_dispatch:
    
    permissions:
      contents: read
    
    jobs:
      contrast:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v6
          - uses: actions/setup-node@v6
            with:
              node-version: 24
          - name: Check color contrast
            run: node check-contrast.mjs contrast-pairs.json
            env:
              AURIC_API_KEY: ${{ secrets.AURIC_API_KEY }}
    

    Download contrast.yml

A real run

A failure, and its fix

The script’s output on the sample pairs, from running it against the API’s own server. One pair fails, so the job fails, and the line says what to change.

The job’s log

PASS  Body text: #1F2937 on #FFFFFF is 14.67:1, needs 4.5:1
FAIL  Muted text: #9CA3AF on #FFFFFF is 2.53:1, needs 4.5:1 - nearest passing text color #6D7787 (4.52:1)
PASS  Button label: #FFFFFF on #4338CA is 7.90:1, needs 4.5:1
PASS  Input border: #6B7280 on #FFFFFF is 4.83:1, needs 3:1
3 of 4 pairs pass.

Exit code 1: a pair fails

The annotation GitHub shows on the pull request

::error title=Contrast - Muted text::#9CA3AF on #FFFFFF is 2.53:1, needs 4.5:1. Try the nearest passing text color #6D7787 (4.52:1).

After changing the muted text to the suggested color

PASS  Body text: #1F2937 on #FFFFFF is 14.67:1, needs 4.5:1
PASS  Muted text: #6D7787 on #FFFFFF is 4.52:1, needs 4.5:1
PASS  Button label: #FFFFFF on #4338CA is 7.90:1, needs 4.5:1
PASS  Input border: #6B7280 on #FFFFFF is 4.83:1, needs 3:1
4 of 4 pairs pass.

Exit code 0: every pair passes

With no key

check-contrast: set AURIC_API_KEY to an API key (a repository secret on CI)

Exit code 2: the check could not run

What the exit codes mean

The script’s exit codes
CodeMeaning
0Every pair passes. The job is green.
1A pair is below its ratio. The job fails, and each failing line names the nearest passing text color.
2The check could not run: no key, a pairs file it cannot read, or an answer from the API that is not a result. Nothing was judged.

Remediation

Fixing a failure

  1. Change the token, not each use

    The suggestion keeps the hue and moves the lightness. Put it in the token the pair comes from, so every use follows, then run the check again.

  2. If the suggestion is wrong for the brand, move the background

    A ratio has two sides. A darker or lighter surface can bring the pair up without touching the brand color.

  3. When no text color passes

    On a mid-tone background neither black nor white may reach the ratio; the line then says to change the background.

    The script on a pair no text color can fix

    FAIL  Small print, AAA: #FFFFFF on #6B7280 is 4.83:1, needs 7:1 - no text color on this background reaches it; change the background
    ::error title=Contrast - Small print, AAA::#FFFFFF on #6B7280 is 4.83:1, needs 7:1. No text color on this background reaches it: change the background.
    0 of 1 pairs pass.

How to fix a contrast failure, with a reproduction

Limits

What it does not do

  • It checks the pairs you list, not your pages: a pair you leave out is not checked. Keep the list next to your tokens, and run an audit on the pages too.
  • The ratio is WCAG 2’s. The API measures APCA too; this script does not use it.
  • Each run spends credits from your plan, which is why the workflow runs on pull requests and not on every push.
  • The workflow file was parsed as YAML, and the script was run against the API’s server; neither was run on GitHub for this page.

Privacy and access

What it sends

  • The colors in your pairs file, and nothing else, go to the API with your key.
  • The key stays in your CI’s secret store; the script never prints it.

Go further

The full reference

Checked against

Where these facts come from

Versions, requirements and examples on this page were checked on . The script was run with Node.js 24.11.0 against the API’s own server, started in local open mode: on the sample pairs it exited with 1, with the suggested color applied with 0, with no key with 2, and on a pair no text color can fix with 1. The output shown is from those runs. The workflow file was parsed with the yaml package 2.9.1; it was not run on GitHub.

Found a mistake? Write to us and we will correct it.