
How to Fix Common Accessibility Issues on Shopify: A Technical Guide
This guide explains ten useful accessibility patterns to review on a Shopify storefront, why each matters, and a possible technical remediation. It is not based on a published representative sample and does not imply that every store contains these issues. Confirm the finding, source ownership, and customer impact before changing code.
1. Missing Alternative Text on Images
WCAG Criterion: 1.1.1 Non-text Content | axe-core Rule: image-alt | Severity: Critical
Meaningful images without usable text alternatives can prevent screen-reader users from understanding products or content. Decorative images should instead use an empty alternative, and functional icons need an accessible control name. Inspect both the stored metadata and the published theme output.
Manual fix: In your Liquid template, ensure image tags include the alt attribute:
{% assign img_alt = image.alt | escape | default: product.title | escape %}
<img src="{{ image | img_url: '640x' }}" alt="{{ img_alt }}" width="640" height="640">
AccessComply handling: Eligible missing-alt findings may be reported on reached pages. AI-assisted description or template candidates are limited to supported paths and plan entitlements, require merchant review, and must be verified in the published storefront.
2. Insufficient Color Contrast
WCAG Criterion: 1.4.3 Contrast (Minimum) | axe-core Rule: color-contrast | Severity: Serious
Text that does not meet the 4.5:1 contrast ratio against its background is difficult or impossible to read for users with low vision or color deficiencies. This violation frequently appears in sale badges, footer text, placeholder text, and secondary navigation links.
Manual fix: Adjust the color values in your CSS. For example, change light gray text on a white background from color: #999 to color: #595959 to achieve 4.5:1 against white.
AccessComply handling: A safely source-mapped first-party color candidate may be proposed for merchant approval. Shared variables, imagery, component states, and brand decisions still require manual review and post-change testing.
3. Missing Form Labels
WCAG Criterion: 1.3.1 Info and Relationships | axe-core Rule: label | Severity: Critical
Form inputs without associated labels are announced as "edit text" by screen readers with no indication of what information to enter. This is especially problematic in checkout, account login, and newsletter signup forms.
Manual fix: Wrap each input in a label or use the for attribute:
<label for="email">Email address</label>
<input id="email" type="email" name="customer[email]">
AccessComply handling: Automated rules may report eligible unlabeled controls. Selecting accurate label text and changing first- or third-party markup requires source ownership, safe mapping, merchant approval, and manual form-flow testing.
4. Icon Buttons Without Accessible Names
WCAG Criterion: 4.1.2 Name, Role, Value | axe-core Rule: button-name | Severity: Critical
Cart icons, search icons, and hamburger menus that contain only an SVG icon with no text alternative announce as "button" to screen readers — providing no context. Users with visual impairments cannot tell what the button does.
Manual fix: Add aria-label to icon buttons and aria-hidden to the decorative SVG:
<button type="button" aria-label="Open cart">
<svg aria-hidden="true" focusable="false">
<!-- cart SVG path -->
</svg>
</button>
AccessComply handling: Automated rules may report eligible unnamed controls. The correct accessible name and mechanism require contextual review; third-party app controls usually require vendor coordination.
5. Missing Skip Navigation
WCAG Criterion: 2.4.1 Bypass Blocks | axe-core Rule: bypass | Severity: Serious
Without a skip navigation link, keyboard users must tab through your entire header — logo, navigation links, account icon, cart icon — on every page before reaching the main content. For stores with extensive navigation, this creates a frustrating barrier.
Manual fix: Add a skip link as the first element in layout/theme.liquid:
<a href="#main-content" class="skip-link">Skip to main content</a>
With CSS to show it on focus:
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 9999;
text-decoration: none;
}
.skip-link:focus {
top: 0;
}
Add the target ID to your main content section: <main id="main-content" tabindex="-1">.
AccessComply handling: A skip-link candidate may be offered when the relevant first-party template and target are safely source-mapped. A merchant must approve the change and verify it with keyboard and assistive technology.
6. Missing Page Language
WCAG Criterion: 3.1.1 Language of Page | axe-core Rule: html-has-lang | Severity: Serious
Screen readers use the lang attribute on the <html> element to determine which language rules and pronunciation to use. Missing or incorrect language attributes cause screen readers to mispronounce content, which is particularly severe for non-English stores.
Manual fix: In layout/theme.liquid, ensure:
<html lang="{{ request.locale.iso_code }}">
The request.locale.iso_code Liquid variable provides the correct ISO 639-1 language code (e.g., en, fr, de) based on the active store locale.
AccessComply handling: Missing or invalid page-language attributes are automated candidates; the actual locale and multilingual behavior must be verified before a supported source change.
7. Duplicate IDs
WCAG Criterion: 4.1.1 Parsing | axe-core Rule: duplicate-id | Severity: Serious
When the same id attribute value appears multiple times on a page, ARIA attributes that reference those IDs (like aria-labelledby and aria-describedby) break. Screen readers and assistive technologies rely on unique IDs to build accurate accessibility trees.
Repeated sections or custom product grids can generate duplicate IDs when a template does not include a stable unique suffix. Confirm whether any ID reference is ambiguous before editing the source.
Manual fix: In Liquid templates that generate IDs dynamically, include a unique identifier:
<div id="product-{{ product.id }}-{{ forloop.index }}">
AccessComply handling: Automated rules may report eligible duplicate-ID candidates. Changing IDs can break labels, descriptions, scripts, deep links, and app integrations, so remediation needs dependency review and post-change testing.
8. Missing Focus Indicators
WCAG Criterion: 2.4.7 Focus Visible | axe-core Rule: focus-visible | Severity: Serious
Custom theme or app CSS can suppress the browser focus ring with outline: none or outline: 0. If no visible replacement is supplied, keyboard users may not be able to see which element is focused.
Manual fix: Remove outline: none from your CSS or replace it with a visible custom focus style:
/* Remove this: */
*:focus { outline: none; }
/* Add this instead: */
*:focus-visible {
outline: 2px solid #F97316;
outline-offset: 2px;
border-radius: 2px;
}
AccessComply handling: Eligible source-mapped focus-style candidates may be proposed, but contrast, area, clipping, forced-colors behavior, and component-specific states need manual verification.
9. Missing Frame Titles
WCAG Criterion: 4.1.2 Name, Role, Value | axe-core Rule: frame-title | Severity: Serious
Inline frames (<iframe>) without title attributes announce as "frame" to screen readers with no description of their content. Shopify stores commonly embed iframes for reviews (Trustpilot, Judge.me), maps, videos, and chat widgets.
Manual fix: Add an accurate descriptive title to each meaningful iframe you own:
<iframe src="https://widgets.trustpilot.com/..." title="Trustpilot reviews"></iframe>
AccessComply handling: An automated rule may report untitled frames on reached pages. The correct title and ability to change app-owned embeds require manual or vendor review.
10. Table Structure Issues
WCAG Criterion: 1.3.1 Info and Relationships | axe-core Rule: td-headers-attr | Severity: Serious
Data tables without correctly associated headers can be difficult to understand with a screen reader. Size charts, comparison tables, and specification sheets are useful places to inspect, including responsive or app-rendered versions.
Manual fix: Add <th scope="col"> for column headers and <th scope="row"> for row headers:
<table>
<thead>
<tr>
<th scope="col">Size</th>
<th scope="col">Chest (in)</th>
<th scope="col">Waist (in)</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">S</th>
<td>34-36</td>
<td>28-30</td>
</tr>
</tbody>
</table>
AccessComply handling: Automated rules may flag structural candidates, but identifying true headers and relationships requires understanding the data. Treat this as manual content review unless the source mapping and semantics are unambiguous.
Automating vs. Manual Fixes
Some first-party findings above may be eligible for a supported, safely source-mapped candidate. Eligibility is not a promise that a change is safe or sufficient. Complex form flows, third-party widgets, documents, captions, and custom interactive components need developer, merchant, specialist, or vendor review.
An AccessComply scan can report eligible automated findings and sample locations on public pages and states it reaches. It cannot determine every issue, route, dynamic state, or manual-only requirement, and a proposed change still needs approval and post-change verification.
Further Reading
Find the storefront issues holding back growth
Scan SEO, speed, and accessibility by page. Review supported fixes before they run, keep saved originals, and verify the live result afterward.