Web accessibility is not a nice-to-have feature. In the UK, the Equality Act 2010 requires businesses to make reasonable adjustments to ensure their services are accessible to disabled people. This includes websites and online shops. Beyond legal compliance, accessible design improves usability for all customers — better navigation, clearer content, and more intuitive interactions benefit everyone.
For Shopify stores, accessibility compliance requires attention at every level: theme code, content structure, colour choices, interactive elements, and third-party app behaviour. The Web Content Accessibility Guidelines (WCAG) 2.2 at Level AA is the standard most organisations target, and it is the benchmark UK courts reference when assessing digital accessibility claims.
This guide covers the technical requirements for making a Shopify store WCAG 2.2 compliant, with code examples you can implement in your custom Shopify theme. We work on accessibility as part of our web design and Shopify development services.
Why accessibility matters for ecommerce
Approximately 16 million people in the UK have a disability. That represents a significant portion of your potential customer base. The Click-Away Pound survey found that 69% of disabled customers will leave a website that is not accessible, taking their spending power elsewhere. For UK ecommerce, the estimated lost revenue from inaccessible websites exceeds £17 billion annually.
Beyond the commercial case, there are legal, ethical, and SEO benefits. Accessible websites tend to have better structured HTML, which search engines can parse more effectively. Proper heading hierarchies, descriptive alt text, and semantic markup all contribute to better organic rankings — the same principles that underpin technical SEO.
Accessibility is not about building a separate experience for disabled users. It is about building one experience that works for everyone.
WCAG 2.2 overview for Shopify
WCAG 2.2 is organised around four principles (POUR):
- Perceivable — information must be presentable to users in ways they can perceive (text alternatives, captions, adaptable content).
- Operable — interface components must be operable (keyboard accessible, enough time, no seizure-causing content).
- Understandable — information and interface operation must be understandable (readable, predictable, input assistance).
- Robust — content must be robust enough to be interpreted by assistive technologies (valid HTML, ARIA support).
New criteria in WCAG 2.2 particularly relevant to Shopify stores:
- Focus Not Obscured (2.4.11) — when an element receives keyboard focus, it must not be entirely hidden by sticky headers, modals, or other overlays. This is commonly violated by Shopify sticky headers and announcement bars.
- Dragging Movements (2.5.7) — any functionality that uses dragging must have a single-pointer alternative. Relevant for drag-to-reorder cart items or image galleries with drag navigation.
- Target Size Minimum (2.5.8) — interactive targets must be at least 24x24 CSS pixels. This affects mobile navigation links, product option selectors, and quantity controls.
Keyboard navigation
Every interactive element on your Shopify store must be operable with a keyboard alone. This includes navigation menus, product image galleries, variant selectors, add-to-cart buttons, filters, and the cart drawer.
/* Ensure focus indicators are visible and clear */
*:focus-visible {
outline: 3px solid #2D5A27;
outline-offset: 2px;
border-radius: 2px;
}
/* Never remove outlines entirely */
/* BAD: *:focus { outline: none; } */
/* Skip link for keyboard users */
.skip-link {
position: absolute;
left: -9999px;
top: 0;
z-index: 9999;
padding: 1rem 2rem;
background: #2D5A27;
color: #fff;
font-weight: 700;
text-decoration: none;
}
.skip-link:focus {
left: 0;
}
Focus trapping in modals
Cart drawers, quick-view modals, and search overlays must trap keyboard focus. When a modal is open, Tab and Shift+Tab should cycle through focusable elements within the modal only:
// Trap focus within modal dialogs (cart drawer, quick view, etc.)
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), ' +
'select:not([disabled]), textarea:not([disabled]), ' +
'[tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
modal.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeModal(modal);
return;
}
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) {
last.focus();
e.preventDefault();
}
} else {
if (document.activeElement === last) {
first.focus();
e.preventDefault();
}
}
});
first.focus();
}
Screen reader compatibility
Screen readers interpret your HTML structure. Semantic HTML is the foundation of screen reader compatibility. Use the correct HTML elements for their intended purpose:
<!-- Use semantic elements, not generic divs -->
<nav aria-label="Main navigation">...</nav>
<main id="main">...</main>
<article>...</article>
<aside aria-label="Related products">...</aside>
<footer>...</footer>
<!-- Announce dynamic content changes -->
<div aria-live="polite" aria-atomic="true"
class="visually-hidden" id="cart-status">
<!-- Updated via JavaScript when cart changes -->
<!-- e.g., "Item added to cart. Cart total: 2 items." -->
</div>
<!-- Visually hidden text for screen readers -->
.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;
}
ARIA live regions for cart updates
When a customer adds a product to their cart, screen reader users need to know the action was successful. Use aria-live regions to announce changes:
// After successful add to cart
function announceCartUpdate(productName, cartCount) {
var status = document.getElementById('cart-status');
status.textContent = productName + ' added to cart. ' +
'Cart now contains ' + cartCount + ' items.';
}
Colour contrast requirements
WCAG 2.2 Level AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18px+ or 14px+ bold). This applies to all text including product prices, navigation, buttons, and form labels.
/* Common contrast failures on Shopify stores */
/* BAD: Light grey text on white — ratio 2.85:1 */
.product-price { color: #999; background: #fff; }
/* GOOD: Sufficient contrast — ratio 7.0:1 */
.product-price { color: #595959; background: #fff; }
/* BAD: Green text on green background */
.sale-badge { color: #6B9F5B; background: #E8F5E9; } /* Ratio: 2.4:1 */
/* GOOD: Darker green for sufficient contrast */
.sale-badge { color: #2D5A27; background: #E8F5E9; } /* Ratio: 5.6:1 */
/* Interactive element states must also meet contrast */
.btn:hover, .btn:focus {
/* Ensure hover/focus states maintain contrast */
background: #1A3D16; /* Darker, higher contrast */
color: #fff;
}
Tools for checking contrast: the WebAIM Contrast Checker, Chrome DevTools (inspect element → Accessibility pane), and the axe DevTools browser extension all report contrast ratios.
ARIA attributes in Liquid templates
ARIA (Accessible Rich Internet Applications) attributes provide additional context to assistive technologies. Use ARIA judiciously — the first rule of ARIA is “don’t use ARIA if native HTML can do the job.”
<!-- Product variant selector with proper labelling -->
<fieldset>
<legend>Select size</legend>
{% for value in product.options_by_name['Size'].values %}
<label class="variant-option">
<input type="radio" name="Size"
value="{{ value }}"
{% if forloop.first %}checked{% endif %}
aria-describedby="size-guide-link">
<span>{{ value }}</span>
</label>
{% endfor %}
<a href="#size-guide" id="size-guide-link">View size guide</a>
</fieldset>
<!-- Add to cart button with loading state -->
<button type="submit"
class="btn-add-to-cart"
aria-busy="false"
aria-label="Add {{ product.title | escape }} to cart">
<span class="btn-text">Add to cart</span>
<span class="btn-loading visually-hidden" aria-hidden="true">
Adding...
</span>
</button>
<!-- Product image gallery -->
<div role="region" aria-label="Product images"
aria-roledescription="carousel">
<div role="group" aria-label="Image 1 of {{ product.images.size }}">
<img src="{{ image | image_url: width: 800 }}"
alt="{{ image.alt | default: product.title | escape }}"
width="800" height="800">
</div>
</div>
Accessible forms and inputs
Every form input needs an associated label. Placeholder text alone is not sufficient — it disappears when the user starts typing and is not reliably read by all screen readers.
<!-- Always use visible labels -->
<div class="form-group">
<label for="email">Email address</label>
<input type="email" id="email" name="email"
autocomplete="email"
required
aria-required="true">
</div>
<!-- Error messages must be programmatically associated -->
<div class="form-group">
<label for="email">Email address</label>
<input type="email" id="email"
aria-invalid="true"
aria-describedby="email-error">
<span id="email-error" role="alert" class="form-error">
Please enter a valid email address
</span>
</div>
<!-- Quantity selector with accessible controls -->
<div class="quantity-selector" role="group"
aria-label="Quantity for {{ product.title | escape }}">
<button type="button" aria-label="Decrease quantity"
onclick="decrementQty()">−</button>
<input type="number" id="quantity" name="quantity"
value="1" min="1" max="99"
aria-label="Quantity">
<button type="button" aria-label="Increase quantity"
onclick="incrementQty()">+</button>
</div>
Images and media accessibility
Every product image needs descriptive alt text. Decorative images should have empty alt attributes (alt=""). For product images, describe what the image shows:
<!-- Descriptive alt text for product images -->
<img src="{{ image | image_url: width: 800 }}"
alt="Navy organic cotton t-shirt, crew neck, shown on model — front view"
width="800" height="800"
loading="lazy">
<!-- Decorative images -->
<img src="divider.svg" alt="" role="presentation">
<!-- SVG icons need accessible names -->
<svg aria-hidden="true" focusable="false">...</svg>
<!-- Or if the icon IS the label: -->
<button aria-label="Search">
<svg aria-hidden="true"><!-- search icon --></svg>
</button>
<!-- Video with captions -->
<video controls>
<source src="product-demo.mp4" type="video/mp4">
<track kind="captions" src="captions-en.vtt"
srclang="en" label="English" default>
Your browser does not support video.
</video>
Automated testing tools
Automated tools catch approximately 30–40% of accessibility issues. They are essential for identifying low-hanging fruit but cannot replace manual testing:
- axe DevTools — browser extension that scans pages for WCAG violations. Integrated with Chrome and Firefox DevTools.
- WAVE — web accessibility evaluation tool from WebAIM. Provides visual feedback overlaid on your page.
- Lighthouse — built into Chrome DevTools. Includes an accessibility audit alongside performance and SEO.
- Pa11y — command-line tool for automated accessibility testing. Integrates into CI/CD pipelines.
# Run Pa11y against key Shopify pages
npx pa11y https://yourstore.com
npx pa11y https://yourstore.com/collections/all
npx pa11y https://yourstore.com/products/example-product
npx pa11y https://yourstore.com/cart
# Run against multiple pages with a config file
# .pa11yci.json
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 30000
},
"urls": [
"https://yourstore.com",
"https://yourstore.com/collections/all",
"https://yourstore.com/products/example-product"
]
}
npx pa11y-ci
Manual testing checklist
Automated tools miss the majority of accessibility issues. Manual testing is essential for comprehensive coverage:
Keyboard testing
- Navigate the entire purchase flow using only Tab, Enter, Space, Escape, and Arrow keys.
- Verify all interactive elements have visible focus indicators.
- Check that focus order is logical and predictable.
- Confirm modal dialogs trap focus and can be closed with Escape.
- Verify the skip link works and targets the main content area.
Screen reader testing
- Test with NVDA (Windows, free), VoiceOver (Mac/iOS, built-in), or JAWS (Windows, paid).
- Navigate the homepage, a collection page, a product page, and the cart.
- Verify all form inputs are properly labelled.
- Check that dynamic content changes (add to cart, error messages) are announced.
- Confirm images have meaningful alt text.
Visual testing
- Zoom the page to 200% and verify all content remains accessible.
- Test with high contrast mode enabled (Windows) or Increased Contrast (macOS).
- Verify text reflows correctly at narrow viewport widths.
- Check that no information is conveyed by colour alone (e.g., red for errors must also have text/icon).
Accessibility compliance is not a one-off project. Every theme update, new app installation, and content change can introduce accessibility regressions. Build accessibility testing into your regular QA process and treat it with the same priority as performance and checkout optimisation.
If you need help auditing your Shopify store’s accessibility or building an accessible theme from scratch, get in touch. We build accessible Shopify stores as standard through our web design and Shopify development services.