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

Author avatarVijaygopal Balasa
Updated September 26, 2026
10 min read

The WebAIM Million (February 2026) found low-contrast text on 83.9% of its sampled home pages. 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.

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

Useful contrast patterns to inspect include:

  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 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 — Let AccessComply write the colour you approve

Best for: text in your theme’s own sections, on a Shopify theme built with Liquid.

AccessComply measures each failing pair on the rendered page. Where moving the text colour a little further from its background reaches the ratio, it writes that colour as one CSS rule scoped to the theme section where the text was measured, after you approve. Where no small move reaches it, as with light text on a mid tone, the finding lists the nearest passing text colour and the nearest passing background; pick the text colour and the next fix run writes it the same way, on every finding with those two colours. The theme is backed up before the first write, and the changed pages are scanned again. Colours inside another app’s widget, colours set inline on an element, and backgrounds stay with you.

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

AccessComply does not change your theme settings or shared colour variables. Its rule is scoped to the section where the failing text was measured, so other components that use the same variable keep their colour. A pass for one pair does not prove every state passes: check hover, focus and any dark mode yourself.

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 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, accessibility and AI shopping readiness by page. Review supported fixes before they run, keep saved originals, and verify the live result afterward.

Free checkers: Shopify accessibility checker · Website accessibility checker · Products not showing in ChatGPT

Vijaygopal Balasa, Founder, AccessComply
Written by

Vijaygopal Balasa

Founder, AccessComply

Founder of AccessComply. Builds tools that find and fix supported Shopify accessibility issues in theme code—not through overlays. Focused on practical WCAG 2.2 AA improvements for merchants.

Keep improving after this fix.

Get concise, practical updates on Shopify SEO, speed, accessibility, and safer storefront changes. No daily noise; unsubscribe anytime.

More on Accessibility

All Accessibility posts →