Common accessibility issues in Shopify stores and how to fix them

Most Shopify accessibility problems are not introduced by store owners. They come from theme code: patterns that theme developers ship across thousands of storefronts, multiplied by every product and collection you add. When we scan Shopify stores, we see the same failures across brands on different themes, because the underlying component patterns are shared. This guide covers the ones that come up most often, explains why they happen in the Shopify context, and shows the fix at the code level.

Why Shopify stores have accessibility issues by default

Shopify's own platform code is reasonably accessible. The hosted checkout, for example, is maintained by Shopify's own engineering team and generally passes WCAG 2.1 AA. The problem is in the layer above it: Liquid templates and JavaScript components that theme developers write to differentiate the store experience. A theme can override accessible defaults with custom widget patterns that were tested visually but not with a screen reader or keyboard.

The Dawn theme, Shopify's free starter theme (now in version 14+), improved significantly from older defaults. But even Dawn ships with some patterns that generate automated accessibility violations when used as-is. Paid themes like Prestige, Impulse, and Turbo add additional interactive components, each with their own accessibility track record.

What follows are the issues we measure in automated scans of real Shopify stores.

1. Color and size swatch buttons with no accessible name

This is one of the most common findings on Shopify product pages. A product with 15 color options renders 15 buttons. If each button contains only a small color swatch image or a filled div, with no text and no aria-label, a screen reader user navigating the variant selector hears nothing useful. Axe-core flags this as a button-name violation (WCAG 4.1.2).

The typical Dawn or custom theme pattern looks like this:

<!-- No accessible name -->
<button type="button" class="color-swatch" data-value="Navy">
  <span class="swatch-fill" style="background-color:#1a2744;"></span>
</button>

A screen reader announces "button" with no indication of what the button does or which color it selects. The fix is an aria-label that names the option:

<!-- Accessible -->
<button type="button" class="color-swatch" aria-label="Color: Navy" data-value="Navy">
  <span class="swatch-fill" aria-hidden="true" style="background-color:#1a2744;"></span>
</button>

In Liquid, if your swatch values come from product options, you can generate this dynamically:

{% for value in option.values %}
  <button type="button" class="color-swatch"
    aria-label="Color: {{ value | escape }}"
    data-value="{{ value | escape }}">
    <span class="swatch-fill" aria-hidden="true"
      style="background-color: {{ value | downcase | replace: ' ', '' }}">
    </span>
  </button>
{% endfor %}

The same pattern applies to size buttons rendered as clickable divs or unstyled buttons. If a user can click it to change a variant, it needs an accessible name that identifies what it selects.

2. Collection page quick-add and wishlist buttons

On a collection grid, hover-revealed "quick add" buttons and persistent wishlist icons are nearly always icon-only. The problem compounds on a grid of 30 products: a screen reader user encounters 30 buttons labeled "button" in sequence, with no way to tell which product each relates to.

The pattern that causes violations:

<button class="quick-add-btn">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

<button class="wishlist-btn">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

The fix requires naming each button in a way that identifies the associated product. In Liquid on a collection page:

<button class="quick-add-btn"
  aria-label="Quick add {{ product.title | escape }} to cart">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

<button class="wishlist-btn"
  aria-label="Save {{ product.title | escape }} to wishlist">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

This is also the right fix for quantity increment and decrement buttons on cart pages: rather than "+" and "-", they should read "Increase quantity of [product name]" and "Decrease quantity of [product name]."

3. Newsletter signup forms without labels

Most Shopify themes include a footer email capture section. The typical implementation is a single email input with a submit button. What varies is whether the input has an accessible label. Placeholder text ("Enter your email") is not an accessible label: placeholder disappears as soon as someone starts typing, and screen readers do not treat it as a programmatic association between the field and a description.

Dawn ships with this in some versions:

<!-- No label -->
<form class="newsletter-form">
  <input type="email" name="contact[email]"
    placeholder="Enter your email">
  <button type="submit">Subscribe</button>
</form>

Two approaches fix it. The first uses a visible label above the input:

<label for="newsletter-email">Email address</label>
<input id="newsletter-email" type="email" name="contact[email]"
  placeholder="Enter your email">

The second keeps the label visually hidden but present for screen readers, which is appropriate when the surrounding context makes the purpose obvious:

<label for="newsletter-email" class="visually-hidden">Email address</label>
<input id="newsletter-email" type="email" name="contact[email]"
  placeholder="Enter your email">

The visually-hidden CSS pattern (position absolute, width/height 1px, clip, overflow hidden) hides the element from view without hiding it from assistive technology. Do not use display:none or visibility:hidden for this purpose; those remove the label from the accessibility tree entirely.

4. Announcement bars and promotional banners with no text

A Shopify store's header often includes an announcement bar rotating through promotions ("Free shipping on orders over $75", "SUMMER25 for 25% off"). If those banners contain only background images or SVG decorations, or if the text is embedded as images (for font reasons), the content is inaccessible to screen readers.

The separate issue is interactive banners: a promotional banner that is also a link but has no visible or programmatic link text. Axe-core reports this as a link-name violation.

<!-- No accessible name -->
<a href="/collections/sale" class="promo-banner">
  <img src="summer-sale-banner.jpg">
</a>

The fix depends on the content. If the image conveys essential information:

<a href="/collections/sale" class="promo-banner">
  <img src="summer-sale-banner.jpg" alt="Summer sale: up to 40% off select styles">
</a>

If the same information is in accompanying text on the page:

<a href="/collections/sale" class="promo-banner" aria-label="Shop summer sale">
  <img src="summer-sale-banner.jpg" alt="">
</a>

5. Cart drawer focus management

Most Shopify themes implement a slide-out cart drawer instead of a full-page cart. The drawer is a modal in functional terms: it appears above the page content and contains interactive elements (quantity controls, remove buttons, checkout link). WCAG 2.1 success criterion 2.4.3 requires that when a modal opens, keyboard focus moves into it; when it closes, focus returns to the element that opened it.

Themes frequently get this wrong in both directions. When the drawer opens, focus stays on the "add to cart" button that triggered it, leaving keyboard users on a page behind a visible overlay they cannot navigate past. When the drawer closes, focus is either lost (goes to the top of the page) or left inside an element that no longer exists.

The pattern for correct focus management in JavaScript:

// When the cart drawer opens
function openCartDrawer() {
  cartDrawer.removeAttribute('hidden');
  cartDrawer.setAttribute('aria-modal', 'true');
  // Move focus to the first interactive element, or the drawer itself
  cartDrawer.querySelector('button, a, input').focus();
}

// When the cart drawer closes
function closeCartDrawer(triggerEl) {
  cartDrawer.setAttribute('hidden', '');
  // Return focus to the element that opened the drawer
  if (triggerEl) triggerEl.focus();
}

The drawer itself should have role="dialog" and an aria-label or aria-labelledby pointing to a heading inside it so screen readers announce the context when focus lands there.

A note on keyboard trapping: A fully accessible modal also traps keyboard focus inside the dialog while it is open (Tab and Shift+Tab cycle through elements inside the drawer, not through the rest of the page). This requires a focus-trap implementation. Several open-source libraries provide it; it is not the default behavior of a div, even one with role="dialog".

6. Product image galleries

Shopify product pages almost always include a main image with thumbnail navigation below or to the side. The accessibility issues here fall into two categories.

The first is thumbnail buttons that have no accessible name, the same button-name problem as swatches. A thumbnail button containing only an image should have an alt text on the image, which then becomes the accessible name for the button:

<button class="thumbnail-btn">
  <img src="product-angle-2.jpg"
    alt="Linen Tote Bag, side view">
</button>

The second is carousel navigation controls: "previous" and "next" buttons that contain only arrows. These need aria-label values that describe what they do in context:

<button class="gallery-prev"
  aria-label="Previous product image">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

<button class="gallery-next"
  aria-label="Next product image">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

Checking your own store

You can catch a meaningful fraction of these issues without external tools. Tab through your product page and collection grid with a keyboard only. If you cannot reach the color swatches, wishlist buttons, or cart controls without a mouse, those elements are inaccessible to keyboard users. If you reach a button but hear nothing announced in a screen reader, it lacks an accessible name.

The axe DevTools browser extension (free, available for Chrome and Firefox) flags most of the patterns described here automatically. Run it on your product page and your collection grid; those two pages account for the majority of the findings we see in full-site scans.

Automated scanning catches these issues across your full site systematically, with specific element locations and fix guidance. If you want a scan of your store, request a free accessibility scan and we will send you the report.