DEV Community

Ahmed
Ahmed

Posted on Originally published at wcagsafe.com

Why Shopify Stores Are Getting ADA Lawsuits in 2026 — and How to Fix It in Code

Why Shopify Stores Are Getting ADA Lawsuits in 2026 — and How to Fix It in Code

Shopify powers over 4.5 million stores. Plaintiffs' law firms know this — and they run automated scanners against them at scale.

In 2025, over 5,000 ADA web accessibility lawsuits were filed in the US. 69% targeted ecommerce stores. Of those, Shopify stores accounted for 32% — the single most-sued platform by share.

If you build or maintain Shopify stores for clients, one of them will get a demand letter. The fix is almost always in the theme code you wrote or touched. Here's what to look for and how to fix it.


Why Shopify Specifically?

Three reasons Shopify stores fail at scale:

1. Themes optimized for looks, not semantics. Most premium themes use heading tags for visual sizing, not document structure. A store with <h3> styled to look like a page title and <h1> buried in a product card is a screen reader nightmare.

2. Third-party apps inject inaccessible markup. Every popup, review widget, and countdown timer is code you didn't write and probably didn't test. One keyboard trap in a "Spin to Win" modal can make an otherwise clean store legally exposed.

3. AJAX patterns create ghost content. Slide-out carts, Quick View modals, and variant pickers update the DOM without telling screen readers. The user hears nothing. The item was added to cart — silently, invisibly.


The 6 Violations That Show Up in Demand Letters

These are the WCAG failures plaintiffs' scanners flag most consistently on Shopify stores.


1. Product Images Without Alt Text

WCAG 1.1.1 (Level A) — the most common, most fixable

In Shopify Liquid, product images often render like this:

<img src="{{ product.featured_image | img_url: '600x' }}">
Enter fullscreen mode Exit fullscreen mode

No alt attribute. Screen readers announce the filename. Fix it:

<img
  src="{{ product.featured_image | img_url: '600x' }}"
  alt="{{ product.featured_image.alt | escape | default: product.title | escape }}"
>
Enter fullscreen mode Exit fullscreen mode

The default filter is your safety net — if the merchant didn't fill in alt text in the admin, it falls back to the product title. Not perfect, but never empty.

For purely decorative images (dividers, backgrounds):

<img src="{{ section.settings.bg_image | img_url: '1400x' }}" alt="" role="presentation">
Enter fullscreen mode Exit fullscreen mode

2. Icon Buttons With No Accessible Name

WCAG 4.1.2 (Level A) — cart, search, wishlist, account icons

The most common pattern:

<a href="/cart" class="header-cart">
  <svg><!-- cart icon --></svg>
</a>
Enter fullscreen mode Exit fullscreen mode

A screen reader announces this as "link" with no destination or purpose. Fix with aria-label:

<a href="/cart" class="header-cart" aria-label="Shopping cart, {{ cart.item_count }} items">
  <svg aria-hidden="true" focusable="false"><!-- cart icon --></svg>
</a>
Enter fullscreen mode Exit fullscreen mode

aria-hidden="true" on the SVG stops the screen reader from trying to read it. focusable="false" prevents IE11 from focusing the SVG directly.


3. Keyboard Traps in Modals and Slide-out Carts

WCAG 2.1.1 (Level A) — the most complained-about pattern

When a modal opens, focus must move inside it. When it closes, focus must return to the trigger. Tab should cycle through elements inside the modal only — not escape to the rest of the page.

A minimal focus trap in vanilla JS:

function trapFocus(element) {
  const focusableElements = element.querySelectorAll(
    'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const firstEl = focusableElements[0];
  const lastEl = focusableElements[focusableElements.length - 1];

  element.addEventListener('keydown', function(e) {
    if (e.key !== 'Tab') return;
    if (e.shiftKey) {
      if (document.activeElement === firstEl) {
        lastEl.focus();
        e.preventDefault();
      }
    } else {
      if (document.activeElement === lastEl) {
        firstEl.focus();
        e.preventDefault();
      }
    }
  });

  firstEl.focus();
}
Enter fullscreen mode Exit fullscreen mode

Call trapFocus(modalElement) when the modal opens. Store the trigger element before opening and return focus to it on close:

const trigger = document.activeElement;
openModal();
trapFocus(modal);

closeButton.addEventListener('click', () => {
  modal.close();
  trigger.focus(); // return focus to where the user was
});
Enter fullscreen mode Exit fullscreen mode

4. AJAX Cart Updates With No Screen Reader Announcement

WCAG 4.1.3 (Level AA) — "I added it but nothing happened"

When a user adds an item to the cart, sighted users see a counter increment or a slide-out appear. Screen reader users hear nothing.

Fix with an aria-live region. Add this to your theme layout once:

<div
  id="cart-notification"
  aria-live="polite"
  aria-atomic="true"
  class="visually-hidden"
></div>
Enter fullscreen mode Exit fullscreen mode

Then write to it after the cart updates:

fetch('/cart/add.js', {
  method: 'POST',
  body: JSON.stringify({ id: variantId, quantity: 1 }),
  headers: { 'Content-Type': 'application/json' }
})
.then(res => res.json())
.then(item => {
  document.getElementById('cart-notification').textContent =
    `${item.title} added to cart.`;
});
Enter fullscreen mode Exit fullscreen mode

aria-live="polite" queues the announcement after current screen reader output finishes. aria-atomic="true" reads the full message, not just the changed text node.

Your .visually-hidden utility (should be in every project):

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
Enter fullscreen mode Exit fullscreen mode

5. Color Contrast Failures on Sale Badges and Buttons

WCAG 1.4.3 (Level AA) — the fastest to fix, often overlooked

White text on a red "Sale" badge is the classic failure. Most red hues don't reach 4.5:1 contrast against white.

Combination Ratio Pass?
White #fff on Red #e53e3e 3.99:1 ❌ Fail
White #fff on Dark Red #9b2335 7.1:1 ✅ Pass
Black #000 on Yellow #ffd700 9.5:1 ✅ Pass

Check every foreground/background combination in your theme CSS. The fix is usually darkening the background by 20–30% or switching to a dark text color.


6. Form Fields With Placeholder-Only Labels

WCAG 1.3.1 (Level A) — placeholder text is not a label

Placeholder text disappears when typing, has no programmatic association with the input, and typically fails contrast. Yet it's used as a label in nearly every third-party Shopify app form.

Bad:

<input type="email" placeholder="Email address">
Enter fullscreen mode Exit fullscreen mode

Good:

<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email" placeholder="name@example.com">
Enter fullscreen mode Exit fullscreen mode

If the design requires no visible label, use visually hidden — not display:none (that hides from screen readers too):

<label for="email" class="visually-hidden">Email address</label>
<input type="email" id="email" placeholder="Email address" autocomplete="email">
Enter fullscreen mode Exit fullscreen mode

How to Audit a Shopify Store in 10 Minutes

  1. Automated scan — Run the store URL through WCAGsafe or axe DevTools. Gets you ~35–40% of issues instantly with plain-English descriptions and WCAG criterion numbers.

  2. Keyboard test — Open the store, put the mouse away. Tab through every interactive element on the homepage, a product page, and the checkout flow. Can you reach everything? Can you complete a purchase?

  3. Screen reader test — NVDA (Windows, free) or VoiceOver (Mac, built-in, Cmd+F5). Navigate a product page and the cart. Listen for image filenames, unlabeled buttons, and silent AJAX updates.

  4. Contrast check — Run the homepage through WebAIM's contrast checker. Prioritize sale badges, CTA buttons, and any text overlaid on images.

The automated scan + keyboard-only test catches 80%+ of what ends up in demand letters.


The One Thing That Won't Fix It

Shopify's App Store has several "accessibility overlay" apps promising one-click ADA compliance. They inject a floating toolbar that claims to fix contrast, add alt text, and improve keyboard navigation dynamically.

They don't work. They mask code problems without fixing the underlying markup. Courts have consistently rejected overlay-based compliance defenses. And plaintiffs' firms specifically scan for overlay presence — because it signals an inaccessible codebase underneath.

Fix the code. There is no shortcut.


What a Demand Letter Actually Cites

The violations cited almost always include:

  • A specific element selector (e.g., .header-cart > svg)
  • The WCAG criterion number (1.1.1, 2.1.1, 4.1.2)
  • A screenshot from an automated scanner

All of those are detectable by any automated tool. The plaintiff's attorney ran the same scanner you just ran. Fix the violations your audit surfaces and you've removed most of the legal surface area.


Originally published at wcagsafe.com/blog/shopify-ada-compliance

Top comments (0)