Automatically Catching Contrast Issues in Pull Requests

As part of my work on LittleLink, I built an automated contrast checker GitHub Action that runs every time a pull request touches brands.css
. It looks for changes to button classes, evaluates the contrast ratios between foreground text and background colors, and ensures buttons meet WCAG minimums—even across both light and dark themes.
Ensuring accessible design isn’t just the right thing to do—it’s a vital part of building inclusive products. I used to think accessibility was something to check off at the end of a project, or embarrassingly enough, not consider it at all. While WCAG guidelines help define what’s accessible, automating that check in LittleLink helps ensure accessibility doesn’t fall through the cracks when a new pull request is opened.
How It Works
This custom workflow hooks into pull requests using a GitHub Action. Here's what it does:
- Detects changes in CSS: It inspects
git diff
to find modified.button-*
classes. - Parses color tokens: For each detected button class, it looks at:
--button-text
--button-background
--button-border
- Checks contrast ratios using WCAG 2.1 guidelines.
- Validates visibility across:
- Text on button background
- Button background on light theme (
#ffffff
) - Button background on dark theme (
#121212
)
- Handles gradients by flagging them for manual review, since contrast calculations on dynamic backgrounds can’t be automated reliably.
- Considers border strokes: If a low-contrast button includes a high-contrast border (e.g. white on dark), the check treats it as passing.
Sample Output
Here’s how different scenarios look when a PR runs:
✅ Fully Passing

A high-contrast button like newButton
passes all checks—even across themes. If there's a contrast fail but a high-contrast border is present (e.g. white stroke on a dark theme), it's treated as resolved.
⚠️ Manual Review Required

Buttons using background-image
(gradients) are flagged and require a manual review to confirm readability.
❌ Failing Contrast

If contrast fails and no corrective border is present, the PR fails. In this example, newButton
has a text/background contrast ratio of 1.16, far below the required 4.5. A stroke is recommended to fix it.
The GitHub Action
The action is defined in contrast-check.yml
and contains a script that:
- Parses color definitions from CSS
- Calculates contrast using WCAG formulas (based on luminance)
- Treats gradient detection as a fail-safe
- Handles normalization and error conditions gracefully
Here’s a snippet:
on:
pull_request:
paths:
- 'css/brands.css'
And the shell script:
check_contrast "#ffffff" "$bg_color" "BUTTON vs LIGHT THEME" "$border_color" "#000000"
check_contrast "#121212" "$bg_color" "BUTTON vs DARK THEME" "$border_color" "#ffffff"
You can view the full script in the repo.
Why I Built This
LittleLink has dozens of button styles contributed by the community, and it's easy to introduce a visually appealing style that isn’t readable to everyone. Rather than reviewing them manually, this tool automates the tedium and flags any problems immediately during PR review.
It also doubles as a learning tool—offering real-time feedback and guidance like:
“Contrast ratio 1.16 fails WCAG — Recommend adding a #ffffff
stroke.”
Message Board