
Accessibility Commerce
- 62 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Make an online store WCAG 2.1 AA compliant with screen reader support, keyboard navigation, focus management, and accessible cart and checkout flows.
About
A skill for bringing ecommerce storefronts to WCAG 2.1 AA compliance across screen readers, keyboard nav, and accessible checkout. A developer uses it to remediate a11y audit failures and reduce ADA/AODA/EAA legal risk.
- Per-platform a11y approaches (themes, plugins, ARIA patterns)
- Audit with axe/WAVE and test with NVDA and VoiceOver
Accessibility Commerce by the numbers
- 62 all-time installs (skills.sh)
- Ranked #1,201 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill accessibility-commerceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 62 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Make an online store WCAG 2.1 AA compliant with screen reader support, keyboard navigation, focus management, and accessible cart and checkout flows.
Files
Accessibility for E-commerce
Overview
WCAG 2.1 Level AA compliance covers screen reader announcements for dynamic cart updates, keyboard navigation for interactive components (carousels, quantity steppers, modals), focus management, and color contrast requirements. Accessible stores reduce legal risk under ADA, AODA, and EAA, and typically convert better across all users.
When to Use This Skill
- When an accessibility audit reveals WCAG violations blocking legal compliance (ADA, AODA, EAA)
- When screen reader users report inability to complete purchases
- When keyboard-only users cannot navigate the checkout flow
- When automated tools (axe, WAVE) surface critical issues that need remediation
- When building a new storefront and baking in accessibility from the start
Core Instructions
Step 1: Determine the merchant's platform and choose the right approach
| Platform | Recommended Approach | Why |
|---|---|---|
| Shopify | Use a WCAG-compliant theme (Dawn, Sense) from the Theme Store; install AccessiBe or EqualWeb app for automated overlays; edit Liquid for custom fixes | Shopify's built-in themes have improved a11y significantly; theme editor + Liquid edits handle most issues |
| WooCommerce | Choose an accessible theme (Astra, Kadence); install WP Accessibility plugin (free) for skip links, ARIA roles, and form labels | WP Accessibility adds critical fixes without code; Gutenberg blocks have reasonable built-in accessibility |
| BigCommerce | Use Cornerstone theme (most accessible); enable the built-in accessibility checker in the Theme Editor; install ADA accessibility apps from the marketplace | Cornerstone meets most WCAG AA requirements out of the box |
| Custom / Headless | Implement ARIA patterns manually using the techniques in Step 4; test with NVDA+Firefox and VoiceOver+Safari | Full control requires full responsibility — use the component patterns below |
Step 2: Run an accessibility audit
Before making changes, identify what needs fixing:
1. Automated scan: Run axe DevTools browser extension on your homepage, product page, cart, and checkout 2. WAVE tool: Visit wave.webaim.org and scan your store URL 3. Keyboard test: Tab through your entire checkout without a mouse — every interactive element must be reachable and operable 4. Screen reader test: Test with VoiceOver (Mac/iOS) or NVDA (Windows/Firefox) — navigate your product listing and complete a purchase
Common issues found on most stores:
- Images without meaningful alt text
- Color-only indicators (e.g., red "out of stock" with no text)
- Form fields without labels
- Modals that don't trap focus or respond to Escape
- Add-to-cart buttons that don't announce success to screen readers
Step 3: Platform-specific fixes
---
Shopify
Theme-level fixes (no code required): 1. Go to Online Store → Themes → Customize 2. Set alt text on all images: Products → Media → Edit alt text 3. Under Theme settings → Typography, ensure font sizes are at least 16px for body text 4. Enable Skip to content link in Header settings (available in Dawn and most modern themes)
Liquid code fixes for custom themes:
Add ARIA live region for cart updates — edit your cart-notification.liquid or equivalent snippet:
<div aria-live="polite" aria-atomic="true" class="sr-only" id="cart-live-region">
{{ cart.item_count }} items in cart
</div>For color swatches in variant selectors, wrap in a <fieldset> with <legend>:
<fieldset>
<legend>{{ option.name }}: <span>{{ selected_value }}</span></legend>
{% for value in option.values %}
<label class="swatch {% if forloop.first %}selected{% endif %}">
<input type="radio" name="{{ option.name }}" value="{{ value }}"
{% if value == selected_value %}checked{% endif %} />
<span class="sr-only">{{ value }}</span>
<span class="swatch-color" style="background-color: {{ value | downcase }}" aria-hidden="true"></span>
</label>
{% endfor %}
</fieldset>App options:
- AccessiBe (Shopify App Store, ~$49/mo) — automated WCAG 2.1 AA overlay; adds screen reader adjustments and keyboard navigation without theme edits
- EqualWeb — similar overlay approach with a free tier
---
WooCommerce
Plugin-based fixes: 1. Install WP Accessibility (free, wordpress.org) — adds skip navigation links, ARIA landmarks, removes title attributes from links, and fixes form labels 2. Install Accessibility Suite for WooCommerce — adds ARIA labels to cart and checkout elements automatically
Theme-based fixes:
- Use Astra or Kadence theme — both have strong WCAG AA coverage built in
- In Appearance → Customize → Typography, set base font size to 16px minimum
Manual fixes for WooCommerce checkout fields — add to your child theme's functions.php:
// Associate checkout field labels with inputs
add_filter('woocommerce_checkout_fields', function($fields) {
foreach ($fields as $fieldset => &$fieldset_fields) {
foreach ($fieldset_fields as $key => &$field) {
if (!isset($field['label'])) {
$field['label'] = ucfirst(str_replace('_', ' ', $key));
}
}
}
return $fields;
});---
BigCommerce
1. Start with Cornerstone theme — it has the best baseline accessibility of all BigCommerce themes 2. Go to Storefront → My Themes → Customize and enable the accessibility settings panel 3. For color contrast: in Theme Editor, set button background color with sufficient contrast against white text (minimum 4.5:1 ratio) 4. Install an accessibility app from the BigCommerce App Marketplace (search "accessibility") for overlay-style fixes
---
Custom / Headless
Implement these patterns in your React/Vue/Svelte components:
ARIA live region for cart updates:
// Place once at the app root; update message after add-to-cart
export function CartLiveRegion({ message }) {
return (
<div role="status" aria-live="polite" aria-atomic="true"
style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden', clip: 'rect(0,0,0,0)' }}>
{message}
</div>
);
}Accessible variant selector using radio inputs:
<fieldset>
<legend>Color: <strong>{selectedColor}</strong></legend>
{colors.map(color => (
<label key={color.value} className={`swatch ${!color.available ? 'unavailable' : ''}`}>
<input type="radio" name="color" value={color.value}
checked={selectedColor === color.value}
disabled={!color.available}
onChange={() => onColorChange(color.value)}
className="sr-only" />
<span className="swatch-visual" style={{ background: color.hex }} aria-hidden="true" />
<span className="sr-only">{color.label}{!color.available ? ' (out of stock)' : ''}</span>
</label>
))}
</fieldset>Focus management for modals — use native <dialog> which handles focus trapping automatically:
const dialogRef = useRef(null);
useEffect(() => {
if (isOpen) dialogRef.current.showModal();
else dialogRef.current.close();
}, [isOpen]);
return (
<dialog ref={dialogRef} onClose={onClose}>
{/* focus is automatically trapped inside <dialog> */}
<button onClick={onClose} aria-label="Close">×</button>
{children}
</dialog>
);Visible focus indicators (never remove outlines):
:focus-visible {
outline: 3px solid #2b6cb0;
outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }Step 4: Validate and test
1. Re-run the axe scan after changes and verify critical and serious violations are resolved 2. Test keyboard navigation: Tab → all interactive elements reachable; Enter/Space → activates buttons; Escape → closes modals 3. Test with VoiceOver (Mac): Cmd + F5 to enable, navigate using Tab and arrow keys; verify cart updates are announced 4. Check color contrast with the WebAIM Contrast Checker — body text needs 4.5:1, large text 3:1
Best Practices
- Never remove focus outlines — only suppress them for mouse users with
:focus:not(:focus-visible)so keyboard users retain visible focus - Use semantic HTML first —
<button>for actions,<a>for navigation,<table>for tabular data; ARIA attributes cannot fix non-semantic markup - Test with a real screen reader — automated tools catch only 30-40% of real issues; NVDA+Firefox and VoiceOver+Safari are the most common combinations
- Make touch targets at least 44×44px — this also benefits motor-impaired mouse users
- Write descriptive button text — "Add to Cart" is fine; "Click here" is not; screen reader users navigate by button and link text
- Caption all product videos — use auto-captions as a starting point but verify accuracy for product names and pricing
Common Pitfalls
| Problem | Solution |
|---|---|
| Cart count badge announced as "3" with no context | Wrap in aria-label="3 items in cart" or use aria-live="polite" region that announces changes with full context |
| Color swatch selection not announced | Use <input type="radio"> with <fieldset>/<legend> wrapping the swatch group; div/span click handlers are invisible to screen readers |
| Modal focus not trapped | Use the native <dialog> element (96%+ browser support) which provides focus trapping for free |
| Form errors not announced | Add role="alert" to error summary or aria-invalid="true" + aria-describedby on each failing field |
| Overlay accessibility apps cause conflicts | Test thoroughly after installing any overlay app — they sometimes break custom theme JavaScript; validate with axe before and after |
Related Skills
- @responsive-storefront
- @checkout-flow-optimization
- @mega-menu-builder
- @search-autocomplete
{
"context": "Tests whether the agent implements ARIA live regions for cart announcements, the sr-only CSS pattern, message-clearing behaviour, the quantity stepper ARIA roles and labels, and an accessible cart count badge.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Live region role",
"max_score": 8,
"description": "The cart announcement element has role=\"status\" (or role=\"log\") — not just aria-live alone"
},
{
"name": "aria-live polite",
"max_score": 8,
"description": "The live region element uses aria-live=\"polite\" (not \"assertive\")"
},
{
"name": "aria-atomic true",
"max_score": 8,
"description": "The live region element has aria-atomic=\"true\""
},
{
"name": "sr-only CSS present",
"max_score": 8,
"description": "A visually-hidden CSS class is present that includes at minimum: position:absolute, width/height 1px, overflow:hidden, and clip or clip-path"
},
{
"name": "Message clearing",
"max_score": 9,
"description": "After setting the cart announcement message, the code clears it after a short delay (e.g. setTimeout) so the same message can trigger again"
},
{
"name": "Stepper group role",
"max_score": 8,
"description": "The quantity stepper wrapper element has role=\"group\""
},
{
"name": "Stepper group label",
"max_score": 9,
"description": "The stepper group has an aria-label that references the product name"
},
{
"name": "Stepper button labels",
"max_score": 9,
"description": "The decrement and increment buttons each have an aria-label that mentions the action AND the product name (e.g. 'Decrease quantity of ...')"
},
{
"name": "Stepper input label",
"max_score": 8,
"description": "The number input inside the stepper has an aria-label referencing the product name"
},
{
"name": "Cart badge context",
"max_score": 9,
"description": "The cart count badge does NOT expose a bare number — it uses aria-label with full context (e.g. 'N items in cart') OR is wrapped in a live region that announces changes with context"
},
{
"name": "Live region visually hidden",
"max_score": 8,
"description": "The cart live region element is rendered visually hidden (using the sr-only class or equivalent inline styles) so it does not take up visual space"
},
{
"name": "No outline removal",
"max_score": 8,
"description": "The CSS does NOT apply outline:none globally or to :focus without a replacement; if focus styles are reset they use :focus:not(:focus-visible)"
}
]
}
Screen Reader Support for a Shopping Cart Page
Problem/Feature Description
A mid-size online retailer has launched a React-based storefront and recently received complaints from blind and low-vision customers. When users add items to the cart or adjust quantities, their screen readers (NVDA, VoiceOver) announce nothing — shoppers have no way to confirm their cart was updated without tabbing to the cart icon and trying to read a number badge that announces as just "3" with no context. A third-party accessibility audit has marked the cart page as non-compliant and the company needs it fixed before an upcoming compliance review.
The existing cart page renders a list of line items, each with a name, price, and a quantity control (−/+ buttons and a number input). There is also a cart icon in the header that shows a count badge. Your task is to produce an accessible React implementation of this cart page that solves the screen reader announcement problems.
Output Specification
Produce a single self-contained file cart-accessible.jsx (or cart-accessible.tsx) that implements the accessible cart page. The file should include:
- A
CartLiveRegioncomponent (or equivalent) that announces cart state changes - A
QuantitySteppercomponent for adjusting item quantities - A
CartPagecomponent that wires them together, renders a list of at least two sample products, and shows a cart count badge in a header area - All supporting CSS can be placed in an adjacent
cart-accessible.cssfile or inline via a<style>block at the top of the JSX
The components do not need to connect to a real backend; use local React state. The final files should be ready for a developer to drop into a project and inspect.
{
"context": "Tests whether the agent implements requestAnimationFrame-based route-change focus management, the temporary tabindex=-1 pattern for heading focus, a complete trapFocus implementation cycling Tab/Shift+Tab, proper :focus-visible CSS with forced-colors support, and avoids removing focus outlines without a replacement.",
"type": "weighted_checklist",
"checklist": [
{
"name": "requestAnimationFrame usage",
"max_score": 9,
"description": "The route-change focus function wraps the DOM focus call inside requestAnimationFrame (not setTimeout(fn,0) or direct synchronous call)"
},
{
"name": "Targets main h1",
"max_score": 9,
"description": "The route-change focus utility queries for 'main h1' or '[role=\"main\"] h1' to find the heading to focus"
},
{
"name": "Temporary tabindex -1",
"max_score": 9,
"description": "The heading is given tabindex=\"-1\" before calling .focus() so a non-focusable element can receive focus"
},
{
"name": "tabindex removed on blur",
"max_score": 8,
"description": "The tabindex=\"-1\" attribute is removed from the heading after it loses focus (blur listener with { once: true })"
},
{
"name": "Focusable selector in trapFocus",
"max_score": 9,
"description": "The trapFocus function queries focusable descendants using a selector that includes at minimum: a[href], button:not([disabled]), input:not([disabled]), and [tabindex]:not([tabindex=\"-1\"])"
},
{
"name": "Tab cycling",
"max_score": 9,
"description": "The trapFocus implementation moves focus from the last focusable element back to the first when Tab is pressed"
},
{
"name": "Shift+Tab cycling",
"max_score": 9,
"description": "The trapFocus implementation moves focus from the first focusable element to the last when Shift+Tab is pressed"
},
{
"name": "Focus on modal open",
"max_score": 8,
"description": "When the modal opens, focus is moved to the first focusable element inside the modal (or the modal container itself)"
},
{
"name": "focus-visible style",
"max_score": 8,
"description": "global.css defines a :focus-visible rule with a visible outline (e.g. 2px or 3px solid, with outline-offset)"
},
{
"name": "Mouse-only suppression",
"max_score": 9,
"description": "global.css removes the focus ring only for :focus:not(:focus-visible) — NOT via a blanket :focus { outline: none }"
},
{
"name": "Forced-colors support",
"max_score": 9,
"description": "global.css includes a @media (forced-colors: active) block that sets the :focus-visible outline to use a system color (e.g. ButtonText)"
},
{
"name": "No outline none globally",
"max_score": 4,
"description": "global.css does NOT contain a rule that sets outline:none on :focus without a paired :focus-visible alternative in the same file"
}
]
}
Keyboard Navigation Overhaul for a React Storefront
Problem/Feature Description
A React e-commerce storefront uses client-side routing (React Router) and features a quick-view modal that pops up when shoppers click a product card. Keyboard and screen reader users have filed multiple support tickets: after clicking a navigation link the screen reader stays on the link they just activated instead of announcing the new page; and when the quick-view modal opens, pressing Tab escapes the modal and reaches content behind it, while Escape does nothing. A third complaint is that the CSS reset removed all browser focus outlines and no replacement was added, making keyboard navigation invisible.
You have been asked to produce the utility code and CSS that fixes all three issues and integrates them into the storefront. The store is built with React, so solutions should be React-compatible (hooks, refs, useEffect are all fine).
Output Specification
Produce the following files:
lib/focusManagement.js— focus utility functions for route changes and modal focus trappinghooks/useFocusOnRouteChange.js— a React hook that calls the route-change focus utility (can use a dummy route parameter to simulate a route change trigger)Modal.jsx— a modal dialog component that uses the focus trap utility; it should acceptisOpen,onClose, andchildrenprops; render at least two focusable elements inside (e.g. a heading, a close button, and a sample action button)global.css— global CSS that provides visible focus indicators for keyboard users while suppressing the ring for mouse users, including support for Windows High Contrast mode
The files do not need a running dev server — they should be static source files that demonstrate the patterns clearly and consistently.
{
"context": "Tests whether the agent uses fieldset/legend with hidden radio inputs for variant selectors, marks unavailable variants as disabled with sr-only text, and implements accessible form error patterns (aria-describedby, aria-invalid, role=alert) along with WCAG AA compliant color values.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Fieldset wrapper",
"max_score": 8,
"description": "Color and/or size variant selector uses a <fieldset> element as the outer wrapper"
},
{
"name": "Legend present",
"max_score": 8,
"description": "The fieldset contains a <legend> element labelling the group (e.g. 'Color:' or 'Size:')"
},
{
"name": "Radio inputs",
"max_score": 9,
"description": "Each swatch or size option is implemented as <input type=\"radio\"> — NOT as div, span, or button with click handlers"
},
{
"name": "Visual radio hidden",
"max_score": 7,
"description": "The radio inputs are visually hidden (sr-only class or equivalent) while remaining in the DOM and accessible"
},
{
"name": "Swatch visual aria-hidden",
"max_score": 7,
"description": "The purely decorative swatch colour patch (the visual circle/square) has aria-hidden=\"true\""
},
{
"name": "Unavailable variant disabled",
"max_score": 8,
"description": "Out-of-stock or unavailable variant options have the disabled attribute on the radio input"
},
{
"name": "Out-of-stock sr-only text",
"max_score": 8,
"description": "Unavailable options include sr-only text communicating 'out of stock' (or equivalent) to screen reader users"
},
{
"name": "aria-invalid on error",
"max_score": 8,
"description": "Form inputs in error state have aria-invalid=\"true\""
},
{
"name": "aria-describedby for error",
"max_score": 8,
"description": "Form inputs in error state have aria-describedby pointing to the id of the error message element"
},
{
"name": "role=alert on error message",
"max_score": 9,
"description": "The inline error message element has role=\"alert\" so it is announced immediately"
},
{
"name": "WCAG AA color contrast",
"max_score": 10,
"description": "The CSS defines price, sale price, or out-of-stock badge with specific color values that meet WCAG AA (e.g. dark text on white, or verified colour pairs) rather than generic grey or placeholder values"
},
{
"name": "Semantic button/input elements",
"max_score": 10,
"description": "No interactive action triggers (add to cart, submit) are implemented as non-semantic div or span elements — all use <button> or <input type=\"submit\">"
}
]
}
Accessible Product Detail Page: Variant Selection and Checkout Form
Problem/Feature Description
A fashion retailer's development team has built a product detail page and a one-page checkout, but accessibility testers have flagged two major problem areas. First, the color and size selectors are implemented as <div> elements with click handlers — screen reader users cannot determine which color or size is selected, and out-of-stock options are visually greyed out but announced identically to available options. Second, the checkout form shows inline validation errors as floating tooltips that are never read aloud; a JAWS user reported completing the entire form incorrectly because no errors were ever announced.
The team also needs the price, sale price, and availability labels to meet WCAG AA color contrast requirements so the page passes automated audit tools.
Your task is to produce accessible React components that solve these two areas: a variant selector group and a checkout form field with inline error handling. The components should work standalone and reflect production-quality accessibility patterns.
Output Specification
Produce the following files:
ColorSwatchGroup.jsx— an accessible color variant selectorSizeSelector.jsx— an accessible size variant selector (can reuse the same pattern as color if appropriate)CheckoutField.jsx— a reusable form field component with accessible inline error displayCheckoutForm.jsx— a sample checkout form that usesCheckoutFieldfor at least three fields (name, email, card number) and demonstrates client-side validation with errors shown on submitstyles.css— CSS for the above components including color values for price, sale price, and out-of-stock badge
Include sample data (hardcoded color and size options) so the components render without external dependencies.
{
"name": "finsi/accessibility-commerce",
"version": "0.1.0",
"summary": "WCAG 2.1 AA compliance for e-commerce — screen readers, keyboard nav, ARIA for carts",
"skills": {
"accessibility-commerce": {
"path": "SKILL.md"
}
}
}