Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

Accessible color palette for Shopify stores: contrast checked

If you build or maintain Shopify stores for European customers, you have probably seen the same worry in client emails lately: "Is our store ready for the European Accessibility Act?" Nobody can answer that with a color

If you build or maintain Shopify stores for European customers, you have probably seen the same worry in client emails lately: "Is our store ready for the European Accessibility Act?"

Nobody can answer that with a color palette alone, and I will be clear about that below. But contrast is one of the most common, most visible and cheapest accessibility defects to fix, because you can fix it once, at the design-token level, and every page inherits the fix.

So I built a small free kit: two accessible color palettes (light and dark) where every pair is already measured, delivered as JSON tokens, CSS variables, a Tailwind config and a self-test page. In this post you get the palette, the setup for Shopify, and the 20-line script to verify any palette yourself, mine included.

What the rules actually ask for

For colors, the rules most teams care about come from WCAG 2.x, Level AA:

  • Text needs a contrast ratio of at least 4.5:1 against its background. Large text (24px regular, or about 18.66px bold) needs at least 3:1. See WCAG 1.4.3, Contrast (Minimum).
  • UI components and graphics (input borders, focus rings, icons that carry meaning) need at least 3:1 against adjacent colors. See WCAG 1.4.11, Non-text Contrast.

Why does this matter for EU stores? The European Accessibility Act (Directive (EU) 2019/882) has applied since 28 June 2025 to a range of consumer-facing products and services, including e-commerce services. The technical reference most often used for websites is the harmonised standard EN 301 549, which points to WCAG 2.1 Level AA for web content. National rules and exemptions (for example for very small businesses) vary by country, so check your own situation with a qualified professional. This is not legal advice.

The five contrast mistakes I see most in store themes

  1. Placeholder text that is too light. A pale gray placeholder on white often fails 4.5:1.
  2. Light gray input borders. The border is what tells a user "this is a field", so it needs 3:1.
  3. Removed or invisible focus outlines. outline: none with no replacement leaves keyboard users lost.
  4. Brand-color buttons that were never checked in their hover state. The default passes, the hover does not (or the reverse).
  5. Error and sale colors on tinted backgrounds. Red on a pale pink badge looks fine on your monitor and fails the math.

(Disabled controls are exempt from the contrast requirements, which is why you will see greyed-out buttons that do not count.)

The fix at the token level

Instead of patching colors page by page, define every role once, as a pair that has been measured:

/* Minimalist Fashion, light */
[data-palette="fashion"] {
  --cpt-bg: #FAF8F5;
  --cpt-surface: #F0ECE6;
  --cpt-text: #1A1816;          /* 16.70:1 on bg   */
  --cpt-muted: #5A534B;         /*  7.14:1 on bg   */
  --cpt-primary: #1A1816;
  --cpt-on-primary: #FAF8F5;    /* 16.70:1 on primary */
  --cpt-link: #8A2D1C;          /*  8.01:1 on bg   */
  --cpt-border-strong: #8A8379; /*  3.53:1 on bg   */
  --cpt-focus: #2748C4;         /*  7.04:1 on bg   */
}

Two details make this robust:

  • Every foreground is measured on both backgrounds you actually use. A card (--cpt-surface) sits on the page (--cpt-bg), so text, muted text, links and messages are checked on both.
  • State colors are part of the pair list. The button label is checked on the default and the hover background.

Free palette: Contrast-Proof Tokens Lite

The free kit contains:

  • tokens.json: design tokens with 80 color pairs and their ratios (20 pairs × 2 palettes × light and dark)
  • variables.css: native CSS variables, palette switching and automatic dark mode
  • tailwind.config.js: copy-paste Tailwind config (v3.4+, v4 snippet in the comments)
  • test_page.html: a self-test page that recalculates every ratio in your browser

The two palettes:

  • Minimalist Fashion: warm paper-white and ink with a terracotta accent, for editorial, low-ornament stores.
  • SaaS Tools: crisp white and deep slate with an indigo primary and a teal accent, for dashboards and landing pages.

Download it free: https://payhip.com/b/6FTfQ?utm_source=devto&utm_medium=article&utm_campaign=lite
Source: https://github.com/mohnadjib/contrast-proof-tokens-lite

Using it in a Shopify theme (Online Store 2.0)

  1. In the theme code editor, upload variables.css to Assets.
  2. In layout/theme.liquid, add this inside <head>:
{{ 'variables.css' | asset_url | stylesheet_tag }}
  1. Pick a palette on the <html> tag:
<html lang="{{ request.locale.iso_code }}" data-palette="fashion">
  1. Map your theme's selectors to the tokens in your custom CSS. For example:
.my-primary-button {
  background: var(--cpt-primary);
  color: var(--cpt-on-primary);
}
.my-primary-button:hover {
  background: var(--cpt-primary-hover);
}
.my-input {
  border: 2px solid var(--cpt-border-strong);
  background: var(--cpt-bg);
  color: var(--cpt-text);
}
:focus-visible {
  outline: 3px solid var(--cpt-focus);
  outline-offset: 2px;
}

Replace .my-primary-button and .my-input with your theme's real class names, and test on a duplicate of your live theme first.

Don't trust me: verify any palette in 20 lines

This is the exact WCAG formula. Paste it into your browser console or a Node file:

function channel(v) {
  v /= 255;
  return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}
function luminance(hex) {
  const h = hex.replace('#', '');
  const [r, g, b] = [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16));
  return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
}
function contrast(a, b) {
  const [la, lb] = [luminance(a), luminance(b)];
  return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
}

console.log(contrast('#1A1816', '#FAF8F5').toFixed(2)); // 16.70
console.log(contrast('#8A8379', '#FAF8F5').toFixed(2)); // 3.54 (the kit lists 3.53: it rounds down)

Two honest notes: use the exact value when you compare to a threshold (WCAG does not allow rounding up, so 4.499 is a fail), and remember that this checks the colors you pass in, not what is really rendered over a photo or gradient.

What color contrast will not fix

A verified palette removes one class of defects. It does not give your store:

  • keyboard navigation through menus, carts and modals
  • meaningful alt text and labels
  • a logical heading structure
  • accessible forms and error handling
  • reduced-motion support in your animations
  • accessible third-party apps and widgets

Color contrast is only one part of web accessibility. This kit does not guarantee that any website conforms to WCAG, EN 301 549 or the European Accessibility Act, and it is not legal advice. Test your real pages with real tools (and, when you can, with real users).

Your turn

If you build for a specific sector, tell me which one in the comments: I am deciding which palettes go into the full kit first. And if you find a pair that fails in your own check, tell me too. That is the whole point of putting the proof in the box.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.