Shopify Accessibility: 9 WCAG Failures Themes Repeat
The nine WCAG failures Shopify themes repeat most, from product galleries to checkout, with the broken markup and the fix for each.
If you run a Shopify store, you're building on top of someone else's theme code, which means you're also inheriting someone else's accessibility mistakes. Most Shopify themes, custom or off-the-shelf, repeat the same handful of WCAG violations in the same handful of places: product galleries, add-to-cart buttons, filter sidebars, checkout forms, cart drawers.
This matters for two reasons. First, the obvious one: real people using screen readers, keyboard navigation, or voice control can't buy from a store that fights them at checkout. Second, the legal one: e-commerce sites account for roughly 70% of all ADA digital accessibility lawsuits in the US (UsableNet, "ADA Web Lawsuit Trends for 2026," based on 2025 filings), and federal courts saw 3,117 website accessibility lawsuits filed in 2025 alone, up 27% from 2,452 in 2024 (ADA Title III / Seyfarth Shaw, March 2026). Shopify alone powers close to 30% of US e-commerce (Shopify, "The small business shakedown"), which puts a lot of stores in the blast radius of a trend that's still climbing, not leveling off.
None of this means installing a widget and calling it done. An accessibility overlay doesn't touch the underlying markup your theme renders, and it won't fix any of the nine issues below. (We've written about why overlays don't reduce lawsuit risk if you want the deeper version of that argument.) The fixes below work at the HTML/CSS/ARIA level, which means they apply whether you're running Dawn, a purchased theme, or something fully custom. You are fixing what actually gets rendered in the DOM, not writing Liquid.
A quick honest note before we start: fixing these nine won't make your store "100% ADA compliant". Nobody can promise that, and you should be skeptical of anyone who does. What it will do is close the gaps that show up most often in real audits and are most likely to be the difference between a store that works for everyone and one that doesn't.
1. Product images with no alt text (or bad alt text)
Product photography is the first thing a screen reader user needs described, and it's the single most common miss.
<!-- Broken: no alt attribute -->
<img src="/products/blue-runner-shoe-side.jpg">
<!-- Broken: alt text that's just the filename -->
<img src="/products/blue-runner-shoe-side.jpg" alt="blue-runner-shoe-side.jpg">
<!-- Fixed: describes what's actually shown -->
<img src="/products/blue-runner-shoe-side.jpg"
alt="Blue running shoe, side view, showing mesh upper and white sole">
For product galleries with multiple angles of the same item, don't repeat the same alt text on every thumbnail. Describe what's different about each shot (side view, sole detail, worn on foot), or mark repeats as decorative (alt="") if the surrounding thumbnail button already has an accessible name.
How to check alt text across a Shopify store
Shopify gives you two places to write alt text: on each product's media in the admin (open the product, click an image, and use "Add alt text"), and on the images you add to sections in the theme editor (Online Store, then Edit theme). Shopify's help page on alt text covers both.
The catch is that writing it in the admin does not guarantee it reaches the page. The theme's Liquid template decides what goes into the alt attribute, and some templates output alt="" or a product title whatever the admin holds. Store owners run into exactly this on Shopify's forums: alt text entered but missing on certain product images, or flagged as missing by PageSpeed Insights on Dawn. So check what the live page serves, not what the admin says:
- Open a product page and a collection page, right click a product image, choose Inspect, and read the
alton theimgtag. Do the same for the home page hero and any image sections. - Check each template once, not each product: if one product page drops the alt, every product using that template does too.
- Run the page through our alt text checker. It reads the rendered page, so a theme that throws your alt text away shows up as missing even though the admin field is filled in.
If the admin has the text and the page does not, the fix is in the theme code (usually the image snippet passing image.alt), so it goes to whoever maintains the theme.
2. Add-to-cart and quick-buy controls that aren't real buttons
Many themes style a <div> or <a> to look like a button for "Add to Cart" or "Quick Buy," which strips out keyboard access and screen-reader semantics.
<!-- Broken: div with a click handler, unreachable by keyboard -->
<div class="btn-add-to-cart" onclick="addToCart()">Add to Cart</div>
<!-- Fixed: a real button, keyboard-operable and announced correctly -->
<button type="submit" class="btn-add-to-cart">Add to Cart</button>
If it triggers an action, it should be a <button>. If it navigates somewhere, it should be an <a href="...">. Nothing else gives you free keyboard support and correct screen-reader announcement ("button," "link") for that price.
3. Sale badges and price call-outs that fail contrast
"SALE," "-30%," and "New" badges are usually the most stylized text on the page, and often the least readable, especially in the pastel-on-white or white-on-light-gray combos popular in theme design.
/* Broken: 2.02:1 contrast ratio, fails WCAG AA (needs 4.5:1 for normal text) */
.badge-sale { color: #ff8fa3; background: #fff5f5; }
/* Fixed: 5.42:1 contrast ratio */
.badge-sale { color: #c4133c; background: #fff0f2; }
Run any badge, price, or "low stock" label through a contrast checker before shipping a new theme color scheme. These small UI elements get redesigned more casually than body text, and it shows.
4. Filter and facet sidebars with no ARIA state
Collection-page filters (size, color, price range) are usually collapsible sections. Without ARIA, a screen reader has no way to know whether a filter group is open or closed, or that it's a filter at all.
<!-- Broken: no indication of expanded state or relationship -->
<div class="filter-header" onclick="toggleFilter()">Size</div>
<div class="filter-options">...</div>
<!-- Fixed: real button, ARIA state, and a programmatic link between them -->
<button class="filter-header" aria-expanded="false" aria-controls="filter-size">
Size
</button>
<div id="filter-size" class="filter-options" hidden>...</div>
Toggle aria-expanded and the hidden attribute together in your JS. That is the whole fix, with no new markup structure needed.
5. Checkout fields missing labels and autocomplete
Checkout is the highest-stakes moment on the whole site, and it's where placeholder-as-label and missing autocomplete attributes cost the most.
<!-- Broken: placeholder disappears on input, no label, no autocomplete -->
<input type="text" placeholder="Email address">
<!-- Fixed: persistent label, correct autocomplete token -->
<label for="checkout-email">Email address</label>
<input type="email" id="checkout-email" autocomplete="email">
autocomplete isn't just a browser-fill convenience. WCAG 2.2's Redundant Entry and Accessible Authentication criteria specifically target reducing how much users have to re-type, which matters most for people using switch access or voice input.
6. Cart drawers and quick-view modals with no focus trap
The slide-out cart and the "quick view" product modal are both dialogs, and both are frequently built without dialog semantics, so keyboard focus leaks behind the overlay, and Escape does nothing.
<!-- Broken: a styled div with no role, no focus management -->
<div class="cart-drawer">
<button class="close-btn">×</button>
...
</div>
<!-- Fixed: proper dialog role, labeled, keyboard-dismissible -->
<div class="cart-drawer" role="dialog" aria-modal="true" aria-labelledby="cart-heading">
<h2 id="cart-heading">Your cart</h2>
<button class="close-btn" aria-label="Close cart">×</button>
...
</div>
Pair the markup with JS that moves focus into the drawer when it opens, traps Tab inside it, and returns focus to the trigger button on close.
7. Product image carousels with no pause control or keyboard nav
Auto-rotating carousels on product and homepage banners are a double violation risk: moving content with no pause button fails WCAG 2.2.2, and arrow-only navigation with no keyboard equivalent fails 2.1.1.
<!-- Broken: autoplay, no pause, mouse-only arrows -->
<div class="carousel" data-autoplay="true">
<div class="carousel-arrow-prev" onclick="prev()"></div>
<div class="carousel-arrow-next" onclick="next()"></div>
</div>
<!-- Fixed: pause control, real buttons, reachable by keyboard -->
<div class="carousel" data-autoplay="true">
<button class="carousel-pause" aria-label="Pause carousel">Pause</button>
<button class="carousel-arrow-prev" aria-label="Previous slide">‹</button>
<button class="carousel-arrow-next" aria-label="Next slide">›</button>
</div>
If the carousel is purely decorative marketing content, the simplest fix is often to just turn off autoplay entirely, which also tends to help conversion, not just accessibility.
8. Color/size swatches that rely on color alone
Variant swatches (especially color) frequently convey the selected state only through a border color change or a subtle highlight, which fails for anyone with low vision or color blindness.
<!-- Broken: only a color-based ring shows which swatch is selected -->
<button class="swatch swatch--navy" style="border-color: blue;"></button>
<!-- Fixed: text-equivalent state, not just color -->
<button class="swatch swatch--navy" aria-pressed="true">
<span class="visually-hidden">Navy, selected</span>
</button>
The visual ring can stay. Just make sure aria-pressed (or aria-selected, depending on the widget pattern) and an accessible name carry the same information for anyone who can't see the color difference.
9. "Sold out" and stock-level badges shown only as color or icon
A red dot or a grayed-out swatch meaning "out of stock" is invisible information to a screen reader, and easy to miss for low-vision users too.
<!-- Broken: grayscale + a small icon is the only signal -->
<div class="swatch swatch--sold-out"><svg class="icon-x"></svg></div>
<!-- Fixed: same visual treatment, plus a text equivalent -->
<div class="swatch swatch--sold-out">
<span class="visually-hidden">Sold out</span>
</div>
This one is a five-minute fix per template and closes one of the more common "why can't I tell what's in stock" complaints in accessibility audits of retail sites.
Does the European Accessibility Act apply to a Shopify store?
If the store sells to consumers in the EU, very likely yes, unless it is a microenterprise. The European Accessibility Act (Directive (EU) 2019/882) lists e-commerce services among the services it covers from 28 June 2025, and defines them as services provided through websites "at the individual request of a consumer with a view to concluding a consumer contract". An online shop where people buy things fits that definition.
- Being outside the EU does not take a store out of scope. The Directive's definition of a service provider covers anyone who offers a service "to consumers in the Union". What counts is where the customers are, not where the store is registered.
- Microenterprises providing services are exempt. The Directive defines a microenterprise as fewer than 10 people and an annual turnover or balance sheet total of no more than EUR 2 million. A small store under both lines is exempt from the service requirements; one with 12 staff is not, however small its sales.
- The duty sits with the merchant. The Act's obligations for services fall on the service provider, which for a Shopify store is the business selling through it. It sets no separate duty for the theme developer or the makers of the apps you install, so the cart drawer an app injects and the gallery your theme renders are part of the service you offer, and fixing them is your call to make with whoever built them.
- Stores that were already selling are not grandfathered. The June 2025 date applies to the service as it runs now, not only to stores opened after it. The few transition rules are about specific content, such as pre-recorded video and office files published before that date, and contracts and equipment already in place.
The nine fixes above are the practical start, because they come from WCAG criteria that EN 301 549, the European accessibility standard for websites, includes at Level AA (see our guide to EN 301 549 v4.1.1). Our guide on whether the European Accessibility Act applies to your business walks through the scope test step by step, including national rules and what the exemption does not cover. This is a reading of the Directive, not legal advice: for a decision that matters, ask a lawyer who knows the countries you sell into.
Where AccessGuard fits in
You don't have to catch all nine of these by hand on every template, every time you update a theme. AccessGuard runs automated WCAG 2.2 AA checks on your live store in a real browser, flags failures like the ones above with the element and the criterion, and writes suggested fixes on paid plans, so your job shifts from hunting for problems to reviewing and shipping. Automated checks find many problems but cannot confirm conformance, so keep a manual pass over each template as well.
Paste your Shopify store URL and run 3 free scans, no card required. Start your free scan or read how to build accessible forms and how to write alt text for the checkout and product-image fixes in depth.