Back to Blog
Shopify Color Contrast Fix Guide: WCAG 4.5:1 in Liquid + CSS — featured image

Shopify Color Contrast Fix Guide: WCAG 4.5:1 in Liquid + CSS

Vijaygopal Balasa
10 min read

Color contrast is the most-violated WCAG criterion on the public web — 81% of homepages fail per WebAIM's 2024 report. On Shopify storefronts it is the second-most-cited violation in ADA Title III demand letters after missing alt text. The fix is straightforward at the source-code level: find the failing color pairs, compute the closest brand-aligned passing color, write it to the theme's CSS variables. This guide covers the criterion, the math, the four ways to find every failure on a real Shopify store, and the exact Liquid + CSS pattern that fixes it.

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.

Full criterion deep-dive →

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.

Full criterion deep-dive →

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

Across thousands of Shopify storefronts, contrast failures cluster into a predictable set:

  1. Pale grey body text on white. color: #999 on #fff yields 2.85:1 — failing 4.5:1 by a substantial margin. Typical default-theme failure.
  2. 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.
  3. Footer / secondary nav text in a soft grey on a coloured section background. #737373 on #f5f5f5 yields 4.45:1 — failing 4.5:1 by a hair, but failing it consistently.
  4. 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.
  5. Form input borders below 3:1. A pale-grey #dddddd border on #ffffff yields 1.55:1 — far below the 3:1 non-text threshold. Fails 1.4.11.
  6. 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 coverage of every page on the storefront.

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).
  • The closest brand-aligned passing color.

Free tier covers 3 scans per month with no signup required.

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 switch to the "Contrast" panel. WAVE highlights every contrast failure inline on the page with a click-through to the exact ratio.

WAVE is best for quickly auditing the page you're actively editing. For comprehensive coverage of every page on the storefront, use Method 1.

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 — AI auto-fix via the AccessComply API

Best for: any merchant who does not want to manually adjust every failing color.

AccessComply's ColorContrastAgent runs in three steps:

  1. Scan the storefront, identify every text + non-text contrast failure with the exact selector + computed ratio.
  2. For each failure, compute the closest color in the same hue family (HSL space) that meets the WCAG threshold. Typical adjustment: 5-15% lightness shift while preserving hue + saturation.
  3. Write the new color value to the theme's CSS via themeFilesUpsert Shopify Admin API mutation. Backup the file first, write the change, re-scan to verify the violation is resolved.

The brand identity is preserved. The contrast clears the threshold. The audit trail (scan history + fix records + accessibility statement) documents the remediation effort.

The Liquid + CSS pattern — what your theme should look like

Online Store 2.0 themes expose colors as CSS custom properties driven from theme settings. The pattern is:

{%- 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;
}

When AccessComply detects that --color-text and --color-bg produce a sub-4.5:1 ratio, the agent rewrites --color-text (or --color-bg, whichever is more brand-flexible) to a passing color and re-runs the scan to confirm.

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-weight to 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. The scan output should show the previously-flagged criterion as resolved and the computed ratio above the threshold.

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).
  • Theme CSS custom properties drive all colors from settings, not hard-coded values.

Further reading

Free scan available

Scan your store first, then fix issues in the theme

AccessComply finds WCAG issues by page, creates backups before paid fixes, and re-scans before marking violations resolved. No overlay widget.

Vijaygopal Balasa, Founder, AccessComply
Written by

Vijaygopal Balasa

Founder, AccessComply

Founder of AccessComply. Builds AI agents that fix Shopify accessibility violations at the source-code level — not via overlays. Focused on real WCAG 2.2 AA outcomes for merchants.

More on Shopify How-To

See all →