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 to install

Scan your store free, fix violations at the source

AccessComply scans your Shopify store for ADA + EAA / WCAG 2.1 + 2.2 AA violations and applies real source-code fixes — no overlays, no widgets.

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 →