
Shopify Color Contrast Fix Guide: WCAG 4.5:1 in Liquid + CSS
WebAIM's cited 2024 Million report found low-contrast text on 81% of its sampled homepages. That does not establish a Shopify-specific prevalence or legal-demand ranking. Contrast remediation can be straightforward for a known solid-color pair, but gradients, images, opacity, states, and shared variables need careful review. This guide covers the criterion, the math, and ways to find and verify candidates on a real store.
The two contrast criteria — what they cover
WCAG 1.4.3 Contrast (Minimum) — Level AA
Applies to text and images of text. Required ratio:
- 4.5:1 for body text (anything under 18pt regular or 14pt bold).
- 3:1 for large text (18pt+ regular or 14pt+ bold).
- No requirement for: incidental text in inactive UI, pure decoration, logos, or content that is not visible (off-screen, hidden behind a tab, etc).
The 4.5:1 threshold was chosen because it accommodates users with 20/40 vision (the legal threshold for being unable to drive in many jurisdictions) without requiring assistive technology.
WCAG 1.4.11 Non-text Contrast — Level AA
Extends the 3:1 minimum to:
- UI component borders that are required to identify the component — form input outlines, button borders, focus rings.
- Graphical objects required to understand the content — chart segments, status badges, informative icons.
Inactive (disabled) UI is exempt. So is decorative graphic content that conveys no information.
How contrast is calculated — the WCAG luminance formula
Both criteria use the same luminance-ratio formula. Each color is converted from sRGB to relative luminance using:
L = 0.2126 × R_lin + 0.7152 × G_lin + 0.0722 × B_lin
where each channel value (0-1) goes through the sRGB-to-linear transform:
v_lin = v ≤ 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055)^2.4
The contrast ratio between two colors is then:
ratio = (L_lighter + 0.05) / (L_darker + 0.05)
The output runs from 1:1 (identical colors, no contrast) to 21:1 (pure white on pure black). Body text needs 4.5:1; large text needs 3:1.
You don't need to compute this by hand — the free AccessComply contrast checker does it instantly for any two hex colors.
The most common Shopify contrast failures
Useful contrast patterns to inspect include:
- Pale grey body text on white.
color: #999on#fffyields 2.85:1 — failing 4.5:1 by a substantial margin. Typical default-theme failure. - Brand color buttons that look right but fail. A "tasteful" brand orange (
#ea7838) on white yields 2.91:1 for the button text — failing both 4.5:1 (text on button) and 3:1 (button border). Looks fine to the designer, fails for low-vision users. - Footer / secondary nav text in a soft grey on a coloured section background.
#737373on#f5f5f5yields 4.45:1 — failing 4.5:1 by a hair, but failing it consistently. - Sale-tag overlay text on product imagery. White text on a dark transparent overlay over a product photo with variable colors. Whether it passes depends on the photo behind it — sometimes 5:1, sometimes 2.5:1. Not auditable as a single static color pair.
- Form input borders below 3:1. A pale-grey
#ddddddborder on#ffffffyields 1.55:1 — far below the 3:1 non-text threshold. Fails 1.4.11. - Disabled-state buttons that are not actually disabled. A "low-emphasis" button styled like the disabled state but actually clickable fails — the disabled exemption only applies to genuinely inactive controls.
Method 1 — Free AccessComply scan
Best for: quick automated coverage across discoverable storefront routes.
Run the free AccessComply scan on your storefront. The scan is axe-core-driven via Playwright, runs against desktop and mobile viewports, and reports per-failure:
- The exact CSS selector (so you can find the element).
- The foreground and background colors detected.
- The computed contrast ratio.
- Whether the failure is 1.4.3 (text) or 1.4.11 (non-text).
- Enough rendered context to investigate the reported candidate, where the scanner can determine it.
The public scanner checks up to 10 discoverable pages, subject to crawler safety and time limits, with no signup and a limit of 3 scans per day per store. The installed Free app separately includes 3 app scans per calendar month and 3 eligible deterministic fixes per rolling 30 days.
Method 2 — WAVE browser extension
Best for: spot-checking specific pages while editing content.
WAVE is a free browser extension from WebAIM. Install it, navigate to a page on your Shopify storefront, click the WAVE icon, and review the Contrast panel. It can annotate detected page-level candidates; verify complex backgrounds and states manually.
WAVE is best for quickly auditing the page you're actively editing. For broader automated coverage across discoverable storefront pages, use Method 1 and review its reported scan scope.
Method 3 — WebAIM Contrast Checker
Best for: testing a single color pair before committing to a brand decision.
Open the WebAIM Contrast Checker, paste in your foreground and background hex codes, and read off the ratio + WCAG pass/fail status. Use this to validate brand-color decisions before applying them to the theme.
The AccessComply contrast checker is functionally equivalent and runs in the same tab as the rest of the marketing site.
Method 4 — Reviewed AccessComply remediation candidates
Best for: eligible first-party findings that are safely source-mapped and suitable for merchant review.
AccessComply can report eligible automated candidates on public pages and states it reaches. When a first-party theme value is safely source-mapped, it may propose a reviewed color change through a supported Shopify theme path. The merchant approves the change, affected source or prior value is recorded where supported, and a post-change scan checks the reached state again. Shared variables, imagery, interaction states, visual brand decisions, and third-party UI still need manual review.
The Liquid + CSS pattern — what your theme should look like
Some Online Store 2.0 themes expose colors as CSS custom properties driven from theme settings. Inspect the installed theme; the following is an illustrative pattern:
{%- comment -%}
In layout/theme.liquid, declare CSS variables driven from settings.
{%- endcomment -%}
<style>
:root {
--color-text: {{ settings.colors_text | default: '#1a1a1a' }};
--color-bg: {{ settings.colors_background | default: '#ffffff' }};
--color-accent: {{ settings.colors_accent | default: '#ea580c' }};
--color-button-text: {{ settings.colors_button_text | default: '#ffffff' }};
}
</style>
Then in component CSS:
.product-card__title {
color: var(--color-text);
background-color: var(--color-bg);
}
.btn-primary {
background-color: var(--color-accent);
color: var(--color-button-text);
border: 2px solid var(--color-accent);
}
.btn-primary:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
If a safely mapped pair is eligible, AccessComply may propose changing the relevant first-party value for merchant approval and then recheck the reached state. A passing result for one pair does not prove every consumer or state passes.
Common contrast pitfalls — and what to do instead
- Don't use opacity on text to "tone it down".
color: #1a1a1a; opacity: 0.5;produces an effective ratio that depends on the background, hard to audit, and easy to break. Use a darker concrete color instead. - Don't set
font-weightto compensate for low contrast. WCAG large-text rules require 18pt or 14pt bold; "kind of bold" doesn't qualify. - Don't test on white-only backgrounds. The contrast must pass against the actual background the text is rendered over — section backgrounds, hero overlays, sale-tag tints all matter.
- Don't use hover-state contrast as a workaround. The contrast must pass at rest. Hover styles are additive.
- Do test in dark mode. If your theme supports a dark variant, every color pair needs to pass in both light and dark modes.
Verify your fix
After changing a color value, run the free AccessComply scan again and manually retest affected components. Confirm the original reached-state finding, shared-variable consumers, responsive and interaction states, and any third-party output.
Quick checklist
- Every body-text + background pair on the storefront meets ≥4.5:1.
- Every large-text + background pair meets ≥3:1.
- Form input borders, button borders, focus rings meet ≥3:1 against adjacent colors.
- Disabled-style buttons that are actually clickable meet contrast (only genuinely-inactive UI is exempt).
- Color is paired with text/icon/pattern for every status indicator (low stock, error, success).
- Shared theme color variables and hard-coded component colors have both been reviewed.
Further reading
Find the storefront issues holding back growth
Scan SEO, speed, and accessibility by page. Review supported fixes before they run, keep saved originals, and verify the live result afterward.