Color contrast accessibility checks confirm that visitors can read text and identify controls against their backgrounds. Test the rendered page after fonts, themes, hover states, and background images load. A palette can pass in a design file while the deployed component fails because opacity, overlays, or inherited styles changed the final colors.
Use the WCAG ratios as release criteria, keep failing selectors with the test result, and check the states that an automated page load cannot reach. This guide gives developers a production workflow, CSS fixes, and a focused Playwright test.
Color contrast accessibility checklist
Run these checks on each release-critical public route:
- Test normal text against its rendered background at 4.5:1 or higher.
- Test large text at 3:1 or higher, using the WCAG size and weight definitions.
- Check borders, icons, focus indicators, and other visual information needed to identify a control.
- Open menus, dialogs, validation messages, tooltips, and selected states before testing them.
- Repeat the test in light mode, dark mode, and any high-contrast theme that your site supplies.
- Review text over images, gradients, video, and translucent surfaces by hand.
- Confirm that color does not carry the sole cue for errors, status, links, or chart values.
- Save the URL, viewport, state, selector, foreground color, background color, and ratio with each failure.
Add this focused pass to the broader accessibility checks before launch. The broader checklist covers labels, headings, focus, target size, and manual testing that a contrast result cannot answer.
Apply the WCAG contrast ratios
WCAG 2.2 Success Criterion 1.4.3 sets a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large-scale text at Level AA. The criterion exempts incidental text, inactive controls, logos, and text that forms part of a picture with other significant visual content. Read the exact definitions in the WCAG 2.2 standard before classifying an exception.
For CSS, WCAG treats large-scale text as at least 18 point, or at least 14 point and bold. Deque maps those values to 24 CSS pixels and about 19 CSS pixels when its `color-contrast` axe rule evaluates rendered text. Font size alone does not grant the lower 3:1 threshold. Weight and the applicable definition still count.
Controls and graphics use a separate rule. WCAG 2.2 Success Criterion 1.4.11 requires a 3:1 ratio for visual information that a visitor needs to identify a user-interface component, its state, or a meaningful graphic. The W3C non-text contrast guidance explains which adjacent colors to compare.
Treat the ratio as a boundary, then review the page under realistic display conditions. Thin fonts, glare, text over photography, and a dimmed disabled-looking style can remain hard to read near the minimum.
Test rendered states instead of palette swatches
A color-pair checker answers one question about two selected colors. Your page can produce many more pairs through CSS inheritance and component state.
Check the deployed page in this order:
- Load the route with a clean browser profile and wait for web fonts and theme scripts.
- Run an automated accessibility check on the initial viewport.
- Use the keyboard and pointer to expose focus, hover, active, selected, invalid, and expanded states.
- Open overlays and conditional content, then run the check again.
- Inspect any incomplete result with the browser color picker and computed styles.
- Repeat the key path at a narrow viewport and in each supported color theme.
Deque documents several cases that need manual review, including text over background images, obscured elements, images of text, and complex foreground opacity. Its axe rule reports those cases as incomplete or leaves them outside the rule rather than inventing a ratio. Keep incomplete results visible in CI output.
The keyboard accessibility testing checklist helps you expose focus and dialog states in a repeatable order. Test those states because a strong default button style can still lose its label or focus outline when a user interacts with it.
Fix contrast in CSS tokens
Fix the shared color source before adding a one-page override. A token change can repair the same muted text, border, or error color across many components.
:root {
--surface: #ffffff;
--text: #172033;
--text-muted: #526071;
--accent: #2457d6;
--error: #b42318;
--focus: #1d4ed8;
}
.card {
background: var(--surface);
color: var(--text);
}
.card__meta {
color: var(--text-muted);
}
.field[aria-invalid="true"] {
border: 2px solid var(--error);
}
.field[aria-invalid="true"] + .field-error {
color: var(--error);
}
.button:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 3px;
}
Measure each token in the context where the browser renders it. The same foreground can pass on a white page and fail on a tinted card. Alpha colors such as rgb(23 32 51 / 60%) blend with the background, so the hex value in the source does not represent the final foreground color.
Avoid solving a failure by increasing font size until it crosses the large-text threshold. Change the foreground, background, weight, or component treatment while preserving the intended hierarchy. Retest the full state after the CSS change.
Do not use color as the sole signal
A passing ratio does not prove that visitors can understand a color-coded state. WCAG 2.2 Success Criterion 1.4.1 says color cannot serve as the sole visual means of conveying information, indicating an action, or prompting a response. The W3C use-of-color guidance includes form and chart examples.
Pair color with a second cue:
- Add an icon and error message beside an invalid field.
- Underline links inside body copy instead of relying on blue text alone.
- Add text labels or patterns to chart series.
- Use a border, icon, and status text for success and failure notices.
Keep the cue in the DOM with an accessible name or description. A visual symbol without programmatic text can leave screen-reader users with the same missing information.
Add an axe color contrast test to Playwright
Playwright's accessibility testing guide uses @axe-core/playwright to scan the page in its current state. Install the package, then keep a focused test for contrast beside your broader accessibility suite:
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
test("pricing page has no detectable color contrast failures", async ({ page }) => {
await page.goto("https://example.com/pricing");
const initial = await new AxeBuilder({ page })
.withRules(["color-contrast"])
.analyze();
await page.getByRole("button", { name: "Compare plans" }).focus();
const focused = await new AxeBuilder({ page })
.withRules(["color-contrast"])
.analyze();
expect(
[...initial.violations, ...focused.violations],
).toEqual([]);
});
Run the test in a real browser. The axe-core project notes that its color-contrast rule does not work in JSDOM because the rule needs browser layout and computed style information. Store the axe node targets and failure data in the CI artifact so a developer can find the component without repeating the whole scan.
Add routes and interactions with distinct visual systems, such as pricing, documentation, account forms, and dialogs. A single homepage scan will not render every component state. The website regression testing guide shows how to keep a small, reviewable baseline and compare the same deployed paths after a release.
Check color contrast accessibility with Slop Audit
Run Slop Audit on the public URL you plan to ship. During its bounded Browser Run, Slop Audit runs axe against the rendered page and reports text nodes that fail the color-contrast rule, including selector evidence when axe provides it. The report keeps the finding beside the viewport and audit coverage so you can locate the CSS owner, fix the token, and rescan the same URL.
Slop Audit checks one public page without signing in or opening every interactive state. It does not test every background image, certify WCAG conformance, or replace manual review with disabled users. Keep state-specific Playwright tests and human checks for the paths that carry your product's main tasks.
Finish with the website launch checklist, then save the passing result with the release identifier. The next color or theme change should rerun the same public URL and component states.