Accessibility audit · WCAG 2.2 Level A/AA · 2026-11-06

Sample apparel store (anonymised)

Pages tested: 3 · desktop (1366px) and mobile (375px) · axe-core 4.10.2 plus scripted keyboard, structure and reflow checks, reviewed and written up by an AI agent.

11failing rules
596failing instances (all pages, desktop + mobile)
3critical rules
8serious rules
What this report is, and is not. Automated tools catch only part of WCAG; some criteria (meaningful alt text, video captions, reading order, cognitive load) need human judgement. This report is a prioritised list of verified, automatically detectable failures plus scripted checks. It is not a certification, a guarantee of WCAG/ADA/EAA compliance, or legal advice.

1. Fix these first

#IssueSeverityElementsPages
1Buttons must have discernible textcritical2373/3
2Elements must only use permitted ARIA attributesserious1203/3
3Links must have discernible textserious1103/3
4Elements must meet minimum color contrast ratio thresholdsserious503/3
5All touch targets must be 24px large, or leave sufficient spaceserious263/3
6Interactive controls must not be nestedserious183/3
7Images must have alternative textcritical63/3
8Form elements must have labelscritical63/3
9[role="img"] elements must have an alternative textserious93/3
10Scrollable region must have keyboard accessserious83/3

2. Issues in detail

1. Buttons must have discernible text critical

Rule button-name · WCAG 4.1.2 Name, Role, Value (A) · 237 elements on 3 page(s) · desktop + mobile

Who it affects: Screen-reader users hear only "button" and cannot tell what it does.

Where (examples):

https://example-store.com/ (desktop) → ._searchDesktop_vooo2_184 > ._inputWrapper_g4ym7_8 > ._form_g4ym7_1[data-cnstrc-search-form="true"][action="#"] > ._searchIcon_g4ym7_15[data-cnstrc-search-submit-btn="true"]

<button class="_searchIcon_g4ym7_15" data-cnstrc-search-submit-btn="true">

https://example-store.com/ (desktop) → ._search_mncfk_48

<button class="brand-module_button__pMTrr brand-module_minimal__eSLFC brand-module_sm__jM2Lm _search_mncfk_48">

https://example-store.com/ (desktop) → ._close_mncfk_42

<button class="brand-module_button__pMTrr brand-module_minimal-subdued__Og-5C brand-module_sm__jM2Lm _close_mncfk_42">

https://example-store.com/ (desktop) → div[data-visible="true"][data-index="0"][data-active-slide="true"] > ._productCard_1dmli_1._skeleton_1dmli_76._customSwiperSlide_10jlh_122 > ._productCardContent_bumbi_1._skeleton_bumbi_74 > ._footer_bumbi_46._noGap_bumbi_51 > ._priceAndWishlist_bumbi_54 > .product-card-wishlist-btn.brand-module_minimal__eSLFC[data-cnstrc-btn="add_to_wishlist"]

<button class="brand-module_button__pMTrr brand-module_minimal__eSLFC brand-module_sm__jM2Lm product-card-wishlist-btn" style="--wishlist-color:#000000;pointer-events:none" disabled="" data-cnstrc-btn="add_to_wishlist">
How to fix: Give every icon-only button a text name: visible text, a visually-hidden <span>, or aria-label="Open cart". Do not rely on an SVG or icon font alone.

Shopify: icon-only header buttons (e.g. snippets/header-*.liquid or sections/header.liquid)

<!-- before -->
<button class="header__icon header__icon--search">{% render 'icon-search' %}</button>

<!-- after: a visually hidden text label -->
<button class="header__icon header__icon--search" type="button">
  {% render 'icon-search' %}
  <span class="visually-hidden">{{ 'general.search.search' | t }}</span>
</button>

/* add once to your main CSS if your theme lacks it */
.visually-hidden{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

Carousel arrows / close buttons

<button class="slider-button slider-button--next" aria-label="Next slide">…</button>
<button class="drawer__close" aria-label="Close cart">…</button>

Reference: https://dequeuniversity.com/rules/axe/4.10/button-name?application=axeAPI

2. Elements must only use permitted ARIA attributes serious

Rule aria-prohibited-attr · WCAG 4.1.2 (A) · 120 elements on 3 page(s) · desktop + mobile

Who it affects: Labels set with aria-label on elements that cannot carry one are ignored by screen readers.

Where (examples):

https://example-store.com/ (desktop) → div[data-visible="true"][data-index="0"][data-active-slide="true"] > ._productCard_1dmli_1._skeleton_1dmli_76._customSwiperSlide_10jlh_122

<div class="_productCard_1dmli_1 _skeleton_1dmli_76 _customSwiperSlide_10jlh_122" aria-label="Product Card for " style="--background-color:transparent;--background-color-hover:transparent;--backdrop-filter:unset;--skeleton-background-color:#E0E0E0;--skeleton-highlight-color:#F0F0F0">

https://example-store.com/ (desktop) → div[data-visible="true"][data-index="1"][data-active-slide="false"] > ._productCard_1dmli_1._skeleton_1dmli_76._customSwiperSlide_10jlh_122

<div class="_productCard_1dmli_1 _skeleton_1dmli_76 _customSwiperSlide_10jlh_122" aria-label="Product Card for " style="--background-color:transparent;--background-color-hover:transparent;--backdrop-filter:unset;--skeleton-background-color:#E0E0E0;--skeleton-highlight-color:#F0F0F0">

https://example-store.com/ (desktop) → ._mobile_p7ewd_20 > div:nth-child(2) > .brand-module_wrapper__BSQ7a > ._carousel_10jlh_17[gap="16"][pagination="[object Object]"] > .carousel-scroller.brand-module_scroller__G7Z1c > div[data-index="2"][data-active-slide="false"][data-visible="false"] > ._productCard_1dmli_1._skeleton_1dmli_76._customSwiperSlide_10jlh_122

<div class="_productCard_1dmli_1 _skeleton_1dmli_76 _customSwiperSlide_10jlh_122" aria-label="Product Card for " style="--background-color:transparent;--background-color-hover:transparent;--backdrop-filter:unset;--skeleton-background-color:#E0E0E0;--skeleton-highlight-color:#F0F0F0">

https://example-store.com/ (desktop) → ._mobile_p7ewd_20 > div:nth-child(2) > .brand-module_wrapper__BSQ7a > ._carousel_10jlh_17[gap="16"][pagination="[object Object]"] > .carousel-scroller.brand-module_scroller__G7Z1c > div[data-index="3"][data-active-slide="false"][data-visible="false"] > ._productCard_1dmli_1._skeleton_1dmli_76._customSwiperSlide_10jlh_122

<div class="_productCard_1dmli_1 _skeleton_1dmli_76 _customSwiperSlide_10jlh_122" aria-label="Product Card for " style="--background-color:transparent;--background-color-hover:transparent;--backdrop-filter:unset;--skeleton-background-color:#E0E0E0;--skeleton-highlight-color:#F0F0F0">
How to fix: Move the aria-label onto an element with a suitable role, or use visible/visually-hidden text instead.

aria-label on a plain div/span is ignored

<!-- before --> <span class="badge" aria-label="Sale">-20%</span>
<!-- after  --> <span class="badge"><span class="visually-hidden">Sale: </span>-20%</span>

Reference: https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr?application=axeAPI

3. Links must have discernible text serious

Rule link-name · WCAG 2.4.4 Link Purpose / 4.1.2 (A) · 110 elements on 3 page(s) · desktop + mobile

Who it affects: Screen-reader and voice-control users cannot identify or activate the link.

Where (examples):

https://example-store.com/ (desktop) → .swiper-slide-active > ._container_1nl8d_16 > ._overlayWrapper_a9y6m_1 > ._imageContainer_1nl8d_91 > ._image_1nl8d_91 > a[data-discover="true"]

<a href="/collections/new-arrival-styles" data-discover="true">

https://example-store.com/ (desktop) → ._shopByCardLink_15fr9_113[href$="all-casual-shorts"][data-discover="true"]

<a class="_shopByCardLink_15fr9_113" href="/collections/all-casual-shorts" data-discover="true"></a>

https://example-store.com/ (desktop) → ._image_15fr9_66 > a[href$="all-casual-shorts"][data-discover="true"]

<a href="/collections/all-casual-shorts" data-discover="true">

https://example-store.com/ (desktop) → section[data-section-name="HP - Categories 1"] > ._cards_15fr9_27 > ._shopByCard_15fr9_46:nth-child(2) > ._shopByCardLink_15fr9_113[data-discover="true"]

<a class="_shopByCardLink_15fr9_113" href="/collections/mens-collared-shirts" data-discover="true"></a>
How to fix: Add link text or aria-label to image/icon links (e.g. social icons, logo). For image links, give the <img> a meaningful alt.

Social icon links (often snippets/social-icons.liquid)

<a href="{{ settings.social_instagram_link }}" class="list-social__link">
  {% render 'icon-instagram' %}
  <span class="visually-hidden">Instagram</span>
</a>

Logo / image links

<a href="{{ routes.root_url }}"><img src="…" alt="{{ shop.name }} home"></a>

Reference: https://dequeuniversity.com/rules/axe/4.10/link-name?application=axeAPI

4. Elements must meet minimum color contrast ratio thresholds serious

Rule color-contrast · WCAG 1.4.3 Contrast (Minimum) (AA) · 50 elements on 3 page(s) · desktop + mobile

Who it affects: Low-vision users and anyone on a phone in sunlight struggle to read the text.

Where (examples):

https://example-store.com/ (desktop) → ._changeButton_b211o_38 > .brand-module_content__iXfTs

<div class="brand-module_content__iXfTs">CHANGE TO Denmark SITE</div>

https://example-store.com/ (desktop) → ._continueButton_b211o_42

<a class="_continueButton_b211o_42" href="/" data-discover="true">Continue to the United States site</a>

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(1) > ._firstLevelLink_1mwod_16[data-discover="true"]

<a class="_firstLevelLink_1mwod_16" style="--color:#f24392" href="/collections/featured-collections" data-discover="true">Featured </a>

https://example-store.com/ (desktop) → ._simpleLink_1mwod_30._navItem_1mwod_1[target="_blank"]:nth-child(9)

<a class="_navItem_1mwod_1 _simpleLink_1mwod_30" style="color:#9A9846" href="https://www.cheekies.com/?utm_source=brand-nav&amp;utm_medium=referral&amp;utm_campaign=cheekieslaunch" target="_blank">For Her</a>
How to fix: Raise text/background contrast to at least 4.5:1 (3:1 for text ≥ 24px or bold ≥ 18.66px). Usually a single CSS colour token change.

Fix the colour token once (Theme settings → Colors, or CSS variables)

:root{
  /* example: #9a9a9a on white is 2.8:1 → fails; #6b6b6b is 5.3:1 → passes */
  --color-foreground-muted: #6b6b6b;
}
.price__compare, .caption, .announcement-bar__message { color: var(--color-foreground-muted); }

Check each colour pair with the WebAIM contrast checker before publishing.

Reference: https://dequeuniversity.com/rules/axe/4.10/color-contrast?application=axeAPI

5. All touch targets must be 24px large, or leave sufficient space serious

Rule target-size · WCAG 2.5.8 Target Size (Minimum) (AA, WCAG 2.2) · 26 elements on 3 page(s) · desktop + mobile

Who it affects: People with tremors or large fingers mis-tap small controls, especially on mobile.

Where (examples):

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(1)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(2)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(3)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(4)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">
How to fix: Make tap targets at least 24×24 CSS px, or give them enough spacing. Typical culprits: swatches, carousel dots, close icons.

Give small controls a 24×24 px minimum hit area

.swatch, .slider-counter__link, .drawer__close, .quantity__button {
  min-width: 24px; min-height: 24px;
}
/* or keep the visual size and enlarge the hit area */
.slider-counter__link { position: relative; }
.slider-counter__link::after { content:""; position:absolute; inset:-6px; }

Reference: https://dequeuniversity.com/rules/axe/4.10/target-size?application=axeAPI

6. Interactive controls must not be nested serious

Rule nested-interactive · WCAG 4.1.2 Name, Role, Value (A) · 18 elements on 3 page(s) · desktop

Who it affects: Assistive tech cannot reach or announce a control placed inside another control.

Where (examples):

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(1)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(2)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(3)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">

https://example-store.com/ (desktop) → ._link_1mwod_27._navItem_1mwod_1[role="button"]:nth-child(4)

<div class="_navItem_1mwod_1 _link_1mwod_27" role="button" tabindex="0">
How to fix: Do not put buttons/links inside other buttons/links (common in product cards). Restructure so each control is separate.

Product card: don't put a button inside a link

<!-- before: <a href="{{ product.url }}"> … <button>Quick add</button> … </a> -->
<div class="card">
  <a href="{{ product.url }}" class="card__link">{{ product.title }}</a>
  <button type="button" class="quick-add">Quick add <span class="visually-hidden">{{ product.title }}</span></button>
</div>

Reference: https://dequeuniversity.com/rules/axe/4.10/nested-interactive?application=axeAPI

7. Images must have alternative text critical

Rule image-alt · WCAG 1.1.1 Non-text Content (A) · 6 elements on 3 page(s) · desktop + mobile

Who it affects: Screen-reader users get no information about the image (often a product photo).

Where (examples):

https://example-store.com/ (desktop) → .brand-module_container__WFekk > img

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAXCAYAAAARIY8tAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAK5SURBVHgB7VJLSFRhFD7nv9d53CFnmnyMMjkWaooPsoJaKNpGyYjalIVEBBFZ4cbQiBaGVBBCi2rRqnIT2aKgSKqFRlERGZSLHpNlPtIxdR7OdXTu/P/pdxs2jlNLv83l/pzzfec73wFYwf+Ey3v6uGvw
How to fix: Add alt text describing the image; use alt="" for purely decorative images. In Shopify, fill the product media "alt text" field and output {{ image.alt }}.

Shopify: use the media alt text field

{{ product.featured_media | image_url: width: 800 | image_tag: alt: product.featured_media.alt | default: product.title }}

<!-- decorative images only -->
<img src="…" alt="" role="presentation">

Then fill in alt text in Admin → Products → (image) → Add alt text.

Reference: https://dequeuniversity.com/rules/axe/4.10/image-alt?application=axeAPI

8. Form elements must have labels critical

Rule label · WCAG 1.3.1 / 4.1.2 (A) · 6 elements on 3 page(s) · desktop + mobile

Who it affects: Screen-reader users do not know what to type into the field (e.g. newsletter or search).

Where (examples):

https://example-store.com/ (desktop) → ._searchDesktop_vooo2_184 > ._inputWrapper_g4ym7_8 > ._form_g4ym7_1[data-cnstrc-search-form="true"][action="#"] > ._input_g4ym7_8[name="q"][placeholder=""]

<input name="q" class="_input_g4ym7_8" placeholder="" data-cnstrc-search-input="true" value="">

https://example-store.com/ (mobile) → ._searchMobile_vooo2_162 > ._inputWrapper_g4ym7_8 > ._form_g4ym7_1[data-cnstrc-search-form="true"][action="#"] > ._input_g4ym7_8[name="q"][placeholder=""]

<input name="q" class="_input_g4ym7_8" placeholder="" data-cnstrc-search-input="true" value="">
How to fix: Associate a <label for> with each input, or add aria-label. A placeholder is not a label.

Newsletter / search inputs

<label for="NewsletterForm-{{ section.id }}" class="visually-hidden">Email address</label>
<input id="NewsletterForm-{{ section.id }}" type="email" name="contact[email]" autocomplete="email" placeholder="Email">

Reference: https://dequeuniversity.com/rules/axe/4.10/label?application=axeAPI

9. [role="img"] elements must have an alternative text serious

Rule role-img-alt · WCAG 1.1.1 (A) · 9 elements on 3 page(s) · desktop + mobile

Who it affects: Elements marked as images have no text alternative.

Where (examples):

https://example-store.com/ (desktop) → ._lg_yegym_14 > span[role="img"]

<span role="img" style="display:inline-block;font-size:1em;line-height:1em;vertical-align:middle">🇺🇸</span>

https://example-store.com/ (desktop) → ._md_yegym_11 > span[role="img"]

<span role="img" style="display:inline-block;font-size:1em;line-height:1em;vertical-align:middle">🇺🇸</span>
How to fix: Add aria-label to role="img" elements, or aria-hidden="true" if decorative.

Elements with role=img

<div role="img" aria-label="5 out of 5 stars">★★★★★</div>

Reference: https://dequeuniversity.com/rules/axe/4.10/role-img-alt?application=axeAPI

10. Scrollable region must have keyboard access serious

Rule scrollable-region-focusable · WCAG 2.1.1 Keyboard (A) · 8 elements on 3 page(s) · desktop + mobile

Who it affects: Keyboard users cannot scroll a scrollable area (e.g. a horizontal product slider).

Where (examples):

https://example-store.com/ (desktop) → ._mobile_p7ewd_20 > div:nth-child(2) > .brand-module_wrapper__BSQ7a > ._carousel_10jlh_17[gap="16"][pagination="[object Object]"] > .carousel-scroller.brand-module_scroller__G7Z1c

<div class="carousel-scroller brand-module_scroller__G7Z1c" style="--carousel-gap:8px">

https://example-store.com/cart (desktop) → ._mobile_p7ewd_20 > div:nth-child(2) > .brand-module_wrapper__BSQ7a > div[gap="16"][pagination="[object Object]"][data-fs-recommendations-pod-id="nav-trending-new"] > .carousel-scroller.brand-module_scroller__G7Z1c

<div class="carousel-scroller brand-module_scroller__G7Z1c" style="--carousel-gap: 8px;">

https://example-store.com/cart (desktop) → div[data-fs-recommendations-pod-id="view-cart"] > .carousel-scroller.brand-module_scroller__G7Z1c

<div class="carousel-scroller brand-module_scroller__G7Z1c" style="--carousel-gap: 10px;">
How to fix: Make the scroll container focusable (tabindex="0") with an accessible name, or ensure it contains focusable items.

Horizontal sliders

<div class="slider" tabindex="0" role="region" aria-label="Featured products"> … </div>

Reference: https://dequeuniversity.com/rules/axe/4.10/scrollable-region-focusable?application=axeAPI

11. ARIA progressbar nodes must have an accessible name serious

Rule aria-progressbar-name · WCAG 1.1.1 / 4.1.2 (A) · 6 elements on 3 page(s) · desktop + mobile

Who it affects: Progress bars (e.g. free-shipping bar) are announced without meaning.

Where (examples):

https://example-store.com/ (desktop) → .brand-module_progressBar__6Z-Ub

<div class="brand-module_progressBar__6Z-Ub brand-module_md__ksG64" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="brand-module_bar__tnsXU" style="--percentage: 0%;"></div></div>
How to fix: Give role="progressbar" elements an aria-label, e.g. "Progress toward free shipping".

Free-shipping bar

<div role="progressbar" aria-label="Progress toward free shipping" aria-valuemin="0" aria-valuemax="100" aria-valuenow="{{ percent }}"></div>

Reference: https://dequeuniversity.com/rules/axe/4.10/aria-progressbar-name?application=axeAPI

3. Keyboard, structure and reflow checks

PageFinding
https://example-store.com/No visible <h1> heading (1.3.1 / 2.4.6 best practice)
https://example-store.com/No "skip to content" link found among the first in-page links (2.4.1 Bypass Blocks)
https://example-store.com/Horizontal scrolling at 320px width: fails (page 344px wide at 320px) (1.4.10 Reflow)
https://example-store.com/collections/allHeading levels skipped: 1→3 at "ORIGINALS STRETCH SHORT" (1.3.1 best practice)
https://example-store.com/collections/allNo "skip to content" link found among the first in-page links (2.4.1 Bypass Blocks)
https://example-store.com/collections/allAutoplaying media: video (muted), no controls (2.2.2 Pause, Stop, Hide)
https://example-store.com/cartNo visible <h1> heading (1.3.1 / 2.4.6 best practice)
https://example-store.com/cartNo "skip to content" link found among the first in-page links (2.4.1 Bypass Blocks)

4. Pages tested

PageDesktopMobileReflow @320px
https://example-store.com/99 instances / 11 rules85 instances / 9 rulesfails (page 344px wide at 320px)
https://example-store.com/collections/all81 instances / 11 rules87 instances / 10 rulespasses
https://example-store.com/cart130 instances / 11 rules114 instances / 9 rulespasses

5. Next steps

  1. Fix items 1–5 in section 1. They usually live in a few theme files (header, product card, footer) and fix many elements at once.
  2. Reply with the date when fixes are live, and we will re-run the same tests once at no charge within 30 days.
  3. Test checkout and any third-party widgets (reviews, chat, pop-ups) separately; they are often outside your theme.

This audit was produced by an AI agent operated on behalf of the seller. Questions: reply to the delivery email.