Shopify Accessibility Audit Checklist: WCAG 2.2
TABLE OF CONTENTS
- How Do I Audit My Shopify Store for Accessibility?
- Automated Testing Tools: What to Use and What They Miss
- Image and Media Accessibility (WCAG 1.1.1, 1.2.x)
- Color and Visual Design (WCAG 1.4.x)
- Keyboard Navigation and Focus Management (WCAG 2.1.x, 2.4.x)
- Forms, Checkout, and Error Handling (WCAG 1.3.x, 3.3.x)
- ARIA and Semantic HTML (WCAG 4.1.x)
- Mobile and Touch Accessibility (WCAG 2.5.x β New in 2.2)
- Screen Reader Testing Protocol
- Post-Audit Scoring Benchmarks
- Frequently Asked Questions
A comprehensive Shopify accessibility audit combines three layers: automated scanning using tools like axe-core and WAVE, manual screen reader testing with VoiceOver or NVDA, and keyboard-only navigation testing across all interactive elements. Automated tools alone catch only 30β40% of WCAG issues β the rest require human judgment. This checklist covers every WCAG 2.2 Level AA criterion relevant to Shopify stores, with Liquid and JavaScript code examples you can implement directly.
How Do I Audit My Shopify Store for Accessibility?
A comprehensive Shopify accessibility audit requires three layers of testing: automated scanning to catch structural issues, manual screen reader testing to verify assistive technology compatibility, and keyboard-only navigation testing to ensure operability without a mouse. No single tool catches everything β automated scanners detect approximately 30β40% of WCAG violations, according to the W3C's accessibility evaluation resources.
Layer 1: Automated scanning. Run axe-core (via browser extension or Lighthouse), WAVE, and Google Lighthouse on every key page: homepage, collection page, product detail page, cart, checkout, and account pages. Document every finding with the WCAG success criterion reference.
Layer 2: Manual screen reader testing. Navigate your entire purchase flow using VoiceOver (Mac/iOS), NVDA (Windows), or JAWS (Windows). Listen for: whether headings are announced in logical order, whether form fields have labels announced, whether images have meaningful descriptions, whether error messages are announced, and whether dynamic content changes (cart updates, filter results) are communicated.
Layer 3: Keyboard-only testing. Unplug your mouse and navigate your store using only Tab, Shift+Tab, Enter, Space, and arrow keys. Verify: every interactive element is reachable, focus indicators are visible, no keyboard traps exist (especially in modals and menus), and the tab order follows visual reading order.
TestParty's audit process combines all three layers, using a detection split of approximately 60β70% automated and 30% manual testing. In our experience working with 60+ Shopify brands, a typical store has 40β50 automated-detectable issues and approximately 20 issues that require manual identification.
Automated Testing Tools: What to Use and What They Miss
+----------------------------------------------------+--------------------------+----------------------------------------------------+----------------------------------------------------+
| Tool | Best For | What It Catches | What It Misses |
+----------------------------------------------------+--------------------------+----------------------------------------------------+----------------------------------------------------+
| [axe-core](https://github.com/dequelabs/axe-core) | Source code analysis | Missing alt text, contrast failures, missing labels, broken ARIA, heading skips | Keyboard traps, focus order, screen reader quality, cognitive barriers |
+----------------------------------------------------+--------------------------+----------------------------------------------------+----------------------------------------------------+
| [Google Lighthouse](https://developer.chrome.com/docs/lighthouse/) | Quick scoring | Same as axe (uses axe-core engine) + performance metrics | Manual testing criteria, dynamic content, third-party app issues |
+----------------------------------------------------+--------------------------+----------------------------------------------------+----------------------------------------------------+
| [WAVE](https://wave.webaim.org/) | Visual issue overlay | Structural issues, alerts for potential problems, contrast | Context-dependent judgments, interaction testing |
+----------------------------------------------------+--------------------------+----------------------------------------------------+----------------------------------------------------+
| PowerMapper/SortSite | Site-wide scanning | Cross-page issues, broken links + accessibility | Same manual gaps as axe |
+----------------------------------------------------+--------------------------+----------------------------------------------------+----------------------------------------------------+The critical limitation: automated tools cannot evaluate context. They can flag that an image has no alt text, but they cannot determine what the alt text should say. They can detect that a heading level was skipped (H2 β H4), but they cannot tell you whether the heading text accurately describes its section. They can find that a form field lacks a label, but they cannot assess whether the label makes sense to a user.
TestParty is building a Unified Testing Engine that combines 11 scanning engines to maximize automated detection before manual auditors begin their review. Even with this approach, approximately 30% of issues require human judgment.
Image and Media Accessibility (WCAG 1.1.1, 1.2.x)
Every non-decorative image must have meaningful alt text. Every decorative image must have empty alt (`alt=""`). Videos need captions and transcripts. This section is the most common failure point on Shopify product pages.
Product image alt text (Liquid):
{% comment %} WRONG: Default Shopify behavior often uses filename {% endcomment %}
<img src="{{ image | image_url }}" alt="{{ image.alt }}">
{% comment %} RIGHT: Fallback to product title + context {% endcomment %}
<img
src="{{ image | image_url: width: 800 }}"
alt="{% if image.alt != blank %}{{ image.alt | escape }}{% else %}{{ product.title | escape }} - Product Image {{ forloop.index }}{% endif %}"
loading="lazy"
width="{{ image.width }}"
height="{{ image.height }}"
>Decorative images (icons, backgrounds):
{% comment %} Decorative images get empty alt and aria-hidden {% endcomment %}
<img src="{{ 'decorative-divider.svg' | asset_url }}" alt="" aria-hidden="true">SVG accessibility:
{% comment %} SVGs need role and title for screen readers {% endcomment %}
<svg role="img" aria-labelledby="cart-icon-title" focusable="false">
<title id="cart-icon-title">Shopping cart</title>
<!-- SVG paths -->
</svg>
{% comment %} Decorative SVGs should be hidden {% endcomment %}
<svg aria-hidden="true" focusable="false">
<!-- decorative SVG paths -->
</svg>Checklist:
- [ ] All product images have descriptive alt text (not filenames)
- [ ] Decorative images have `alt=""` and `aria-hidden="true"`
- [ ] SVGs have `role="img"` and `<title>` or `aria-label`
- [ ] Video content has captions and transcripts available
- [ ] Image carousels/galleries announce slide changes to screen readers
- [ ] Zoom functionality is keyboard accessible
Color and Visual Design (WCAG 1.4.x)
Color contrast and visual design issues affect the widest range of users β including the 300 million people worldwide with color vision deficiencies and the 2.2 billion with vision impairment, according to the WHO.
Color contrast CSS variables:
/* Accessible color contrast system for Shopify themes */
:root {
/* Text colors β all meet 4.5:1 against white backgrounds */
--color-text-primary: #1a1a1a; /* 16.75:1 ratio */
--color-text-secondary: #4a4a4a; /* 9.73:1 ratio */
--color-text-muted: #6b6b6b; /* 5.74:1 ratio β minimum for body text */
/* Interactive elements β must meet 3:1 against adjacent colors */
--color-link: #0056b3; /* 7.03:1 on white */
--color-link-hover: #003d80; /* 10.69:1 on white */
--color-focus-ring: #0056b3; /* Used for focus indicators */
/* Status colors β never rely on color alone */
--color-error: #c62828; /* 7.16:1 on white */
--color-success: #2e7d32; /* 5.09:1 on white */
--color-sale: #c62828; /* Use with text label, not just color */
}Sale badge example (don't rely on color alone):
{% comment %} WRONG: Color-only sale indicator {% endcomment %}
<span class="sale-price" style="color: red;">{{ product.price | money }}</span>
{% comment %} RIGHT: Color + text label + screen reader context {% endcomment %}
{% if product.compare_at_price > product.price %}
<span class="visually-hidden">Original price</span>
<s class="price--compare">{{ product.compare_at_price | money }}</s>
<span class="visually-hidden">Sale price</span>
<span class="price--sale">{{ product.price | money }}</span>
<span class="badge badge--sale" aria-hidden="true">Sale</span>
{% endif %}Checklist:
- [ ] Body text meets 4.5:1 contrast ratio against background
- [ ] Large text (18px+ bold or 24px+) meets 3:1 contrast ratio
- [ ] UI components and focus indicators meet 3:1 against adjacent colors
- [ ] Information is not conveyed by color alone (sale badges, stock status, errors)
- [ ] Text resizes to 200% without loss of content or functionality
- [ ] Content reflows at 320px viewport width without horizontal scrolling
Keyboard Navigation and Focus Management (WCAG 2.1.x, 2.4.x)
Keyboard accessibility is the foundation of all assistive technology support. If an element cannot be reached and activated with a keyboard, it is inaccessible to screen reader users, switch users, and voice control users.
Skip navigation link (Liquid):
{% comment %} Add as the FIRST element inside <body> {% endcomment %}
<a class="skip-to-content" href="#main-content">
Skip to content
</a>
{% comment %} CSS for skip link β visible only on focus {% endcomment %}
<style>
.skip-to-content {
position: absolute;
top: -100%;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
padding: 1rem 2rem;
background: var(--color-text-primary, #1a1a1a);
color: #ffffff;
text-decoration: none;
font-weight: bold;
border-radius: 0 0 4px 4px;
}
.skip-to-content:focus {
top: 0;
}
</style>
{% comment %} Target element on your main content area {% endcomment %}
<main id="main-content" tabindex="-1">
{{ content_for_layout }}
</main>Focus visible indicator (CSS):
/* Custom focus indicator that meets WCAG 2.4.7 and 2.4.11 */
*:focus-visible {
outline: 3px solid var(--color-focus-ring, #0056b3);
outline-offset: 2px;
border-radius: 2px;
}
/* Ensure focus is not obscured by sticky elements (WCAG 2.4.11) */
header[data-sticky="true"] {
/* Account for sticky header height in scroll calculations */
}
*:focus-visible {
scroll-margin-top: 100px; /* Adjust to sticky header height */
}Modal focus trap (JavaScript):
// Accessible modal focus management for Shopify quick-view, cart drawer, etc.
class AccessibleModal {
constructor(modalElement) {
this.modal = modalElement;
this.focusableElements = this.modal.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
this.firstFocusable = this.focusableElements[0];
this.lastFocusable = this.focusableElements[this.focusableElements.length - 1];
this.previouslyFocused = null;
}
open() {
this.previouslyFocused = document.activeElement;
this.modal.setAttribute('role', 'dialog');
this.modal.setAttribute('aria-modal', 'true');
this.modal.removeAttribute('hidden');
this.firstFocusable.focus();
this.modal.addEventListener('keydown', this.handleKeydown.bind(this));
}
close() {
this.modal.setAttribute('hidden', '');
this.modal.removeEventListener('keydown', this.handleKeydown.bind(this));
if (this.previouslyFocused) {
this.previouslyFocused.focus();
}
}
handleKeydown(event) {
if (event.key === 'Escape') {
this.close();
return;
}
if (event.key !== 'Tab') return;
if (event.shiftKey) {
if (document.activeElement === this.firstFocusable) {
event.preventDefault();
this.lastFocusable.focus();
}
} else {
if (document.activeElement === this.lastFocusable) {
event.preventDefault();
this.firstFocusable.focus();
}
}
}
}Checklist:
- [ ] Skip navigation link is the first focusable element
- [ ] All interactive elements have visible focus indicators
- [ ] Focus indicators are not obscured by sticky headers or overlays (WCAG 2.4.11)
- [ ] Tab order follows visual reading order
- [ ] No keyboard traps (especially in modals, mega menus, cart drawers)
- [ ] Modals trap focus inside and return focus on close
- [ ] Escape key closes modals and drawers
- [ ] Carousels are keyboard navigable with arrow keys
- [ ] Dropdown menus open/close with Enter and Escape
Forms, Checkout, and Error Handling (WCAG 1.3.x, 3.3.x)
Form accessibility is critical for Shopify stores because the purchase flow β the most revenue-sensitive part of your site β is a series of forms. Only 11% of cart and checkout pages meet minimum WCAG standards, according to the 2025 eCommerce Accessibility Study.
Accessible form field (Liquid):
{% comment %} WRONG: Placeholder-only field (no programmatic label) {% endcomment %}
<input type="email" placeholder="Email address" name="email">
{% comment %} RIGHT: Visible label with programmatic association {% endcomment %}
<div class="field">
<label for="newsletter-email">Email address</label>
<input
type="email"
id="newsletter-email"
name="email"
autocomplete="email"
aria-required="true"
aria-describedby="newsletter-email-hint"
>
<span id="newsletter-email-hint" class="field__hint">
We'll never share your email.
</span>
</div>Accessible error handling (Liquid + JavaScript):
{% comment %} Error summary at top of form β announced to screen readers {% endcomment %}
<div
id="form-errors"
role="alert"
aria-live="assertive"
class="form__errors"
{% unless form.errors %}hidden{% endunless %}
>
{% if form.errors %}
<h2>{{ form.errors.size }} error(s) prevented your submission:</h2>
<ul>
{% for error in form.errors %}
<li>
<a href="#{{ error.field }}">{{ error.message }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% comment %} Individual field error β associated with aria-describedby {% endcomment %}
<div class="field {% if form.errors contains 'email' %}field--error{% endif %}">
<label for="contact-email">Email address <span aria-hidden="true">*</span></label>
<input
type="email"
id="contact-email"
name="email"
aria-required="true"
{% if form.errors contains 'email' %}
aria-invalid="true"
aria-describedby="contact-email-error"
{% endif %}
>
{% if form.errors contains 'email' %}
<span id="contact-email-error" class="field__error" role="alert">
Please enter a valid email address.
</span>
{% endif %}
</div>Checklist:
- [ ] All form fields have visible `<label>` elements (not just placeholder text)
- [ ] Labels are programmatically associated via `for`/`id` matching
- [ ] Required fields are indicated with both visual and programmatic cues
- [ ] Error messages identify the specific field and suggest correction
- [ ] Error summary appears at top of form with links to each field
- [ ] Errors use `role="alert"` or `aria-live="assertive"` for screen reader announcement
- [ ] `aria-invalid="true"` is set on fields with errors
- [ ] Autocomplete attributes are set on address, payment, and contact fields
- [ ] CAPTCHA has an accessible alternative (WCAG 3.3.8)
ARIA and Semantic HTML (WCAG 4.1.x)
Proper semantic HTML is more important than ARIA attributes. The first rule of ARIA is: don't use ARIA if you can use a native HTML element instead. Bad ARIA is worse than no ARIA β it creates false expectations for screen reader users.
Semantic landmark structure (Liquid):
{% comment %} Proper landmark structure for Shopify theme layout {% endcomment %}
<body>
<a class="skip-to-content" href="#main-content">Skip to content</a>
<header role="banner">
<nav aria-label="Main navigation">
<!-- Primary navigation -->
</nav>
</header>
{% if template.name == 'index' %}
<nav aria-label="Promotional announcements">
<!-- Announcement bar -->
</nav>
{% endif %}
<main id="main-content" tabindex="-1">
{{ content_for_layout }}
</main>
<aside aria-label="Recently viewed products">
<!-- Recently viewed section -->
</aside>
<footer role="contentinfo">
<nav aria-label="Footer navigation">
<!-- Footer links -->
</nav>
</footer>
</body>ARIA live region for AJAX cart updates:
{% comment %} Cart count announcement for screen readers {% endcomment %}
<div
id="cart-status"
aria-live="polite"
aria-atomic="true"
class="visually-hidden"
>
{{ cart.item_count }} items in cart, total {{ cart.total_price | money }}
</div>// Update the live region when cart changes
function updateCartStatus(itemCount, totalPrice) {
const status = document.getElementById('cart-status');
status.textContent = `${itemCount} item${itemCount !== 1 ? 's' : ''} in cart, total ${totalPrice}`;
}Checklist:
- [ ] One `<h1>` per page, logical heading hierarchy (H1 β H2 β H3)
- [ ] ARIA landmarks for header, nav, main, aside, footer
- [ ] Multiple `<nav>` elements have distinct `aria-label` values
- [ ] ARIA live regions announce dynamic content changes (cart, filters, search)
- [ ] `aria-expanded` on toggle buttons (menus, accordions, drawers)
- [ ] `aria-current="page"` on active navigation links
- [ ] No redundant ARIA (don't add `role="button"` to `<button>`)
- [ ] All custom components use appropriate ARIA patterns (WAI-ARIA Authoring Practices)
Mobile and Touch Accessibility (WCAG 2.5.x β New in 2.2)
WCAG 2.2 added several criteria specifically relevant to mobile Shopify shopping: touch target sizes, dragging alternatives, and pointer gesture alternatives.
Touch target minimum (CSS):
/* WCAG 2.5.8 β Target Size (Minimum): 24x24px with spacing */
/* For ecommerce, aim for 44x44px (WCAG AAA and Apple/Google guidelines) */
.btn,
.product-option__input + label,
.quantity-selector button,
a.product-card__link {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
}
/* Variant selectors β common failure point on product pages */
.variant-selector__option {
min-height: 44px;
min-width: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}Checklist:
- [ ] All interactive targets are at least 24x24 CSS pixels (WCAG 2.5.8 AA)
- [ ] Ideally 44x44px for primary actions (add to cart, checkout, navigation)
- [ ] Swipe gestures have single-tap alternatives (WCAG 2.5.1)
- [ ] Drag operations have non-dragging alternatives (WCAG 2.5.7)
- [ ] No functionality depends on multi-point or path-based gestures without alternative
Screen Reader Testing Protocol
Automated tools cannot replace screen reader testing. Here is the protocol TestParty uses for every Shopify store audit:
VoiceOver (Mac) β Testing product pages:
- Open Safari β Enable VoiceOver (Cmd + F5)
- Navigate to a product page
- Press VO + Right Arrow to read through the page linearly
- Verify: page title announced, heading hierarchy makes sense, product images have descriptive alt text, price is announced clearly, variant selectors are labeled, add-to-cart button is identifiable
- Tab through all interactive elements β verify focus indicators visible
- Add to cart β verify cart update is announced via ARIA live region
NVDA (Windows) β Testing checkout flow:
- Open Chrome or Firefox β Start NVDA
- Navigate to cart page β begin checkout
- Verify: all form fields announce their labels, required fields are indicated, error messages are announced on submission, progress indicator communicates current step
- Complete a test purchase β verify order confirmation is readable
What to listen for across all testing:
- Headings announced in logical order (H1, then H2s, etc.)
- Every image either has a meaningful description or is marked decorative
- Form fields announce their label before the input type
- Buttons announce their purpose ("Add to cart" not just "Submit")
- Dynamic changes (cart update, filter application) are announced
- No unexpected content announced (hidden elements leaking to screen reader)
- Error messages are announced immediately when they appear
Post-Audit Scoring Benchmarks
After remediation, these are the target scores TestParty achieves across its customer base of 60+ Shopify brands:
+--------------------------+----------------------------------------+----------------------------------------------------+
| Tool | Target Score | What It Means |
+--------------------------+----------------------------------------+----------------------------------------------------+
| Google Lighthouse | 90+ (out of 100) | Automated checks passing; good baseline |
+--------------------------+----------------------------------------+----------------------------------------------------+
| WAVE | 5 or fewer errors | Near-zero automated-detectable issues |
+--------------------------+----------------------------------------+----------------------------------------------------+
| axe-core | 3 or fewer issues | Source-level issues resolved |
+--------------------------+----------------------------------------+----------------------------------------------------+
| PowerMapper/SortSite | Under 10 Level A, under 5 Level AA | Comprehensive automated sweep passing |
+--------------------------+----------------------------------------+----------------------------------------------------+
| Manual screen reader | Full flow passable | Key user journeys work with assistive technology |
+--------------------------+----------------------------------------+----------------------------------------------------+
| Keyboard-only | Full flow navigable | Every element reachable and activatable |
+--------------------------+----------------------------------------+----------------------------------------------------+These benchmarks represent the post-remediation state, not the starting point. In our experience, a typical Shopify store starts with a Lighthouse accessibility score of 60β75, WAVE errors of 20β50+, and multiple keyboard traps. TestParty's 14-day initial remediation brings stores to these benchmarks, with 52 weekly AI-powered scans and 12 monthly manual audits maintaining them over time.
For the broader compliance context, see our 2026 Shopify Accessibility Guide. For WCAG 2.2 specifics, see our WCAG 2.2 vs. 2.1 comparison. For checkout-specific issues, see our Shopify checkout accessibility analysis.
Frequently Asked Questions
How many WCAG issues does a typical Shopify store have? In our experience auditing 60+ Shopify brands, a typical store has 40β50 automated-detectable issues and approximately 20 issues requiring manual identification. Dawn, Shopify's most accessible free theme, ships with 30β100 violations depending on configuration. Premium themes average 100β350.
Can automated tools find all accessibility issues? No. Automated tools detect approximately 30β40% of WCAG violations. They catch structural issues like missing alt text, contrast failures, and broken ARIA, but cannot evaluate context-dependent criteria like whether alt text is meaningful, whether heading text describes its section, or whether keyboard focus order makes sense.
How long does a full Shopify accessibility audit take? A thorough audit covering automated scanning, manual screen reader testing, and keyboard navigation testing takes 2β5 days depending on store complexity. TestParty's initial audit is included in the 14-day remediation timeline, with fixes beginning as issues are identified rather than waiting for the audit to complete.
Do I need to audit third-party apps separately? Yes. Third-party Shopify apps inject their own HTML, CSS, and JavaScript and are not reviewed for accessibility by Shopify. Common offenders include review widgets, email pop-ups, chat tools, and upsell carousels. Each installed app should be tested as part of your audit.
What's the most important thing to fix first? Keyboard navigation and focus management. If a user cannot reach the add-to-cart button or navigate the checkout flow with a keyboard, the store is fundamentally unusable for screen reader users, switch users, and anyone who cannot use a mouse. Fix keyboard accessibility first, then forms and labels, then images and color.
Is Lighthouse score enough to prove compliance? No. A Lighthouse accessibility score of 100 does not mean WCAG compliance β it means all automated checks passed. Many critical accessibility requirements (keyboard traps, focus order, screen reader compatibility, cognitive barriers) are not tested by Lighthouse. Use Lighthouse as a baseline, not a certification.
How often should I re-audit after initial remediation? Continuously. Theme updates, new app installations, and content changes introduce new issues. TestParty runs 52 weekly automated scans and 12 monthly manual audits per year. At minimum, re-audit after every theme update, new app installation, and major content change.
What is the "no ARIA is better than bad ARIA" rule? Adding incorrect ARIA attributes creates worse accessibility than having no ARIA at all. A button with `role="link"` confuses screen readers more than a button with no role attribute (which correctly defaults to "button"). Only add ARIA when native HTML elements cannot achieve the desired semantic meaning, and always test with a screen reader after adding ARIA.
This article was produced using TestParty's cyborg approach β AI-assisted research and drafting, validated and refined by our accessibility team. The analysis above represents TestParty's editorial opinions based on publicly available data. As a competitor in the accessibility market, we have a point of view β but we've cited our sources so you can verify every claim independently.
Stay informed
Accessibility insights delivered
straight to your inbox.


Automate the software work for accessibility compliance, end-to-end.
Empowering businesses with seamless digital accessibility solutionsβsimple, inclusive, effective.
Book a Demo