Issues detected by axe-core rules, intentionally introduced for demo/testing purposes. Each issue is marked in the source code with an A11Y-AXE comment on the relevant line.
Rule: Elements must have sufficient color contrast
Impact: Serious
WCAG: 1.4.3 (AA) — minimum contrast ratio of 4.5:1 for normal text
Reference: https://dequeuniversity.com/rules/axe/4.11/color-contrast
| # | Page | File | Element | Foreground | Background | Notes |
|---|---|---|---|---|---|---|
| 1 | Homepage | src/components/HeroBanner.css |
.hero-content p (hero subtitle) |
#c8c0b8 |
#e8e0d8 |
~1.3:1 ratio |
| 2 | Products Page | src/pages/NewPage.css |
.products-found ("X Products Found" text) |
#b0b4b8 |
#ffffff |
~1.9:1 ratio |
| 3 | Products Page | src/components/FilterSidebar.css |
.filter-count (product count in filter options) |
#c8c8c8 |
#ffffff |
~1.4:1 ratio |
| 4 | Product Detail | src/pages/ProductPage.module.css |
.productDescription (product description text) |
#c0c0c0 |
#ffffff |
~1.6:1 ratio |
Rule: Images must have alternative text
Impact: Critical
WCAG: 1.1.1 (A) — Non-text Content
Reference: https://dequeuniversity.com/rules/axe/4.11/image-alt
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Homepage | src/components/HeroBanner.jsx |
<img src="/images/home/New_Tees.png"> |
Missing alt attribute — screen readers will read the filename as content |
| 2 | Homepage | src/components/TheDrop.jsx |
<img src="/images/home/2bags_charms1.png"> |
Missing alt attribute — screen readers will read the filename as content |
Rule: ARIA attributes must conform to valid values
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
Reference: https://dequeuniversity.com/rules/axe/4.11/aria-valid-attr-value
| # | Page | File | Element | Invalid Attribute | Invalid Value | Valid Values |
|---|---|---|---|---|---|---|
| 1 | Homepage | src/components/FeaturedPair.jsx |
<h1 aria-expanded="yes"> |
aria-expanded |
"yes" |
"true" or "false" |
| 2 | Products Page | src/pages/NewPage.jsx |
<div aria-sort="newest" role="columnheader"> |
aria-sort |
"newest" |
"ascending", "descending", "none", "other" |
| 3 | Product Detail | src/pages/ProductPage.jsx |
<ul aria-relevant="changes"> |
aria-relevant |
"changes" |
Space-separated tokens from: additions, removals, text, all |
Rule: IDs used in ARIA and labels must be unique
Impact: Critical
WCAG: 4.1.1 (A) — Parsing
Reference: https://dequeuniversity.com/rules/axe/4.11/duplicate-id-aria
| # | Page | File | Duplicate ID | Elements Affected | Notes |
|---|---|---|---|---|---|
| 1 | Homepage | src/components/FeaturedPair.jsx |
featured-card-label |
Two <p class="featured-eyebrow"> elements (one per card) |
Rendered in a .map() loop |
| 2 | Homepage | src/components/FeaturedPair.jsx |
featured-card-img |
Two <img> elements (one per card) |
Rendered in a .map() loop |
| 3 | Products Page | src/components/FilterSidebar.jsx |
filter-section-title |
<span>Price</span> and <span>Size</span> |
Both referenced via aria-describedby on their parent buttons |
| 4 | Products Page | src/components/FilterSidebar.jsx |
filter-section-title |
(same as above — second occurrence) | Creates two elements with the same ID in the DOM |
Rule:
<html>element must have a lang attribute
Impact: Serious
WCAG: 3.1.1 (A) — Language of Page
Reference: https://dequeuniversity.com/rules/axe/4.11/html-has-lang
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages | public/index.html |
<html> |
Missing lang attribute entirely |
Rule: lang attribute must have a valid value
Impact: Serious
WCAG: 3.1.2 (AA) — Language of Parts
Reference: https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md
| # | Page | File | Element | Invalid Value | Notes |
|---|---|---|---|---|---|
| 1 | Homepage | src/components/TheDrop.jsx |
<p lang="zz"> |
"zz" |
Not a valid BCP 47 language tag |
Rule:
<ul>and<ol>must only directly contain<li>,<script>, or<template>elements
Impact: Serious
WCAG: 1.3.1 (A) — Info and Relationships
Reference: https://dequeuniversity.com/rules/axe/4.11/list
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Homepage | src/components/TrendingCollections.jsx |
<ul class="trending-grid"> |
Contains a <div class="trending-grid-label"> as a direct child instead of only <li> elements |
Rule: Required ARIA attributes must be provided
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
Reference: https://dequeuniversity.com/rules/axe/4.11/aria-required-attr
| # | Page | File | Element | Role | Missing Required Attributes |
|---|---|---|---|---|---|
| 1 | Homepage | src/components/FeaturedPair.jsx |
<span role="checkbox"> |
checkbox |
aria-checked |
| 2 | Homepage | src/components/TheDrop.jsx |
<div role="slider"> |
slider |
aria-valuenow, aria-valuemin, aria-valuemax |
| 3 | Products Page | src/pages/NewPage.jsx |
<div role="spinbutton"> |
spinbutton |
aria-valuenow |
| 4 | Products Page | src/pages/NewPage.jsx |
<div role="combobox"> |
combobox |
aria-controls, aria-expanded |
| 5 | Product Detail | src/pages/ProductPage.jsx |
<span role="meter"> |
meter |
aria-valuenow, aria-valuemin, aria-valuemax |
Issues detected by Evinced engine rules, intentionally introduced for demo/testing purposes. Each issue is marked in the source code with an A11Y-GEN1 comment on the relevant line.
Rule: Semantics of interactable elements must be conveyed by their tag or assigned role; all UI functionality must be accessible by keyboard
Impact: Critical / Serious
WCAG: 1.3.1 (A), 2.1.1 (A), 4.1.2 (A)
References:
Each issue below is a <div> (or similar non-semantic element) used as an interactive control without a proper role attribute and without tabindex, making it invisible to screen readers as an interactive element and inaccessible to keyboard users.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
<div class="icon-btn wishlist-btn"> |
div used as a wishlist-open button — no role="button", no tabindex |
| 2 | All pages (Header) | src/components/Header.jsx |
<div class="icon-btn"> (Search) |
div used as a search button — no role="button", no tabindex |
| 3 | All pages (Header) | src/components/Header.jsx |
<div class="icon-btn"> (Login) |
div used as a login button — no role="button", no tabindex |
| 4 | All pages (Header) | src/components/Header.jsx |
<div class="flag-group"> |
div used as a region-selector toggle — no role="button", no tabindex |
| 5 | All pages (Footer) | src/components/Footer.jsx |
<div class="footer-nav-item"> (Sustainability) |
div used as a navigation item — no role="link", no tabindex |
| 6 | All pages (Footer) | src/components/Footer.jsx |
<div class="footer-nav-item"> (FAQs) |
div used as a navigation item — no role="link", no tabindex |
| 7 | Products Page | src/components/ProductCard.jsx |
<div class="product-card-quick-add"> |
div used as a quick-add button — no role="button", no tabindex |
| 8 | Homepage | src/components/PopularSection.jsx |
<div class="shop-link"> (Shop Drinkware) |
div used as a navigation link — no role="link", no tabindex |
| 9 | Homepage | src/components/PopularSection.jsx |
<div class="shop-link"> (Shop Fun and Games) |
div used as a navigation link — no role="link", no tabindex |
| 10 | Homepage | src/components/PopularSection.jsx |
<div class="shop-link"> (Shop Stationery) |
div used as a navigation link — no role="link", no tabindex |
| 11 | All pages (Cart Drawer) | src/components/CartModal.jsx |
<div class="continueBtn"> |
div used as a continue-shopping button — no role="button", no tabindex |
| 12 | All pages (Wishlist Drawer) | src/components/WishlistModal.jsx |
<div class="removeBtn"> |
div used as a remove-from-wishlist button — no role="button", no tabindex |
| 13 | Checkout | src/pages/CheckoutPage.jsx |
<div class="checkout-continue-btn"> |
div used as a proceed button — no role="button", no tabindex |
| 14 | Checkout | src/pages/CheckoutPage.jsx |
<div class="checkout-back-btn"> |
div used as a back-to-cart button — no role="button", no tabindex |
| 15 | Order Confirmation | src/pages/OrderConfirmationPage.jsx |
<div class="confirm-home-link"> |
div used as a back-to-shop navigation action — no role="link", no tabindex |
Rule: All interactable elements must have an accessible name
Impact: Critical
WCAG: 1.3.1 (A), 2.4.6 (AA), 4.1.2 (A)
Reference: https://knowledge.evinced.com/system-validations/accessible-name
Each issue below is an interactive element whose accessible name has been removed (either aria-label was stripped or the visible text was hidden from assistive technologies via aria-hidden="true"), leaving screen readers and voice control with no hook to identify or announce the control.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
<div class="icon-btn"> (Search) |
Icon-only control — SVG is aria-hidden, visible text is aria-hidden; no accessible name |
| 2 | All pages (Header) | src/components/Header.jsx |
<div class="icon-btn"> (Login) |
Icon-only control — SVG is aria-hidden, visible text is aria-hidden; no accessible name |
| 3 | All pages (Footer) | src/components/Footer.jsx |
<div class="footer-nav-item"> (FAQs) |
Visible label is aria-hidden; no aria-label — no accessible name |
| 4 | Products Page | src/components/ProductCard.jsx |
<Link class="product-card-image-link"> |
aria-label removed from image link — link has no accessible name beyond the child <img> alt, which is insufficient for a standalone link |
| 5 | Homepage | src/components/PopularSection.jsx |
<div class="shop-link"> (Shop Drinkware) |
Visible label is aria-hidden; no aria-label — no accessible name |
| 6 | Homepage | src/components/PopularSection.jsx |
<div class="shop-link"> (Shop Fun and Games) |
Visible label is aria-hidden; no aria-label — no accessible name |
| 7 | Homepage | src/components/PopularSection.jsx |
<div class="shop-link"> (Shop Stationery) |
Visible label is aria-hidden; no aria-label — no accessible name |
| 8 | All pages (Cart Drawer) | src/components/CartModal.jsx |
<button class="closeBtn"> |
Icon-only button — aria-label removed, SVG is aria-hidden; no accessible name |
| 9 | All pages (Wishlist Drawer) | src/components/WishlistModal.jsx |
<button class="closeBtn"> |
Icon-only button — aria-label removed, SVG is aria-hidden; no accessible name |
| 10 | All pages (Cart Drawer) | src/components/CartModal.jsx |
<div class="continueBtn"> |
Interactive div — visible text is aria-hidden; no aria-label — no accessible name |
Issues detected by Evinced engine rules, intentionally introduced for demo/testing purposes. Each issue is marked in the source code with an A11Y-GEN2 comment on the relevant line.
Rule: Navigation patterns must not use
role="menu"orrole="menubar"on their submenu containers — these roles imply application-level widget semantics and are forbidden inside a navigation landmark
Impact: Serious
WCAG: 1.3.1 (A) — Info and Relationships; 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/navigation#forbidden-roles
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
Submenu <ul> elements (Apparel, Lifestyle, Stationery, Collections, Shop by Brand) |
role="menu" applied to submenu <ul> containers inside the main <nav> — the menu role is forbidden in a navigation landmark; assistive technologies will treat the nav as an application widget instead of a navigation region |
Rule: Interactive elements inside a navigation landmark must carry a role of
link,button, orgroup; other roles (e.g.menuitem) are unexpected and break the navigation pattern
Impact: Serious
WCAG: 1.3.1 (A) — Info and Relationships; 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/navigation#unexpected-interactives
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
Submenu <a> elements (30 links across all submenus) |
role="menuitem" applied to <a> elements inside the nav submenus — menuitem is not a permitted role for interactive elements within a navigation landmark; screen readers cannot correctly identify or announce these links as navigation links |
Rule: Custom combobox/select widgets must expose
role="combobox"so assistive technologies identify and announce the control correctly
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/combobox#role
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
Sort <button class="sort-btn"> |
role="combobox" not set — assistive technologies cannot identify the sort trigger as a combobox widget |
Rule: All interactable elements must have an accessible name
Impact: Critical
WCAG: 1.3.1 (A), 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/combobox#accessibility-label
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
Sort <button class="sort-btn"> |
No accessible name (aria-label removed) — screen readers cannot announce the purpose of the sort combobox |
Rule: Combobox triggers must expose
aria-expandedso assistive technologies can announce whether the options list is open or closed
Impact: Serious
WCAG: 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/combobox#combobox-sanity
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
Sort <button class="sort-btn"> |
aria-expanded removed — screen readers cannot determine whether the sort options list is currently open or closed; the combobox state is undetectable |
Rule: Custom checkbox widgets must expose
role="checkbox"so assistive technologies identify and announce the control correctly
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/checkbox#role
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price filter custom checkbox <div> |
role="checkbox" not set — screen readers cannot identify the element as a checkbox |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size filter custom checkbox <div> |
role="checkbox" not set — screen readers cannot identify the element as a checkbox |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand filter custom checkbox <div> |
role="checkbox" not set — screen readers cannot identify the element as a checkbox |
Rule: All interactive elements must be reachable via keyboard tab navigation
Impact: Critical
WCAG: 2.1.1 (A) — Keyboard
Reference: https://knowledge.evinced.com/components/checkbox#focus-sequence
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price filter custom checkbox <div> |
No tabindex — the checkbox is not in the keyboard focus sequence; keyboard-only users cannot reach it |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size filter custom checkbox <div> |
No tabindex — the checkbox is not in the keyboard focus sequence; keyboard-only users cannot reach it |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand filter custom checkbox <div> |
No tabindex — the checkbox is not in the keyboard focus sequence; keyboard-only users cannot reach it |
Rule: All interactable elements must have an accessible name
Impact: Critical
WCAG: 1.3.1 (A), 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/checkbox#accessible-name
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price filter custom checkbox <div> |
No aria-label or associated <label> — screen readers cannot announce what the checkbox controls |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size filter custom checkbox <div> |
No aria-label or associated <label> — screen readers cannot announce what the checkbox controls |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand filter custom checkbox <div> |
No aria-label or associated <label> — screen readers cannot announce what the checkbox controls |
Rule: Elements with
role="checkbox"must exposearia-checkedto convey their checked/unchecked state to assistive technologies
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
Reference: https://knowledge.evinced.com/components/checkbox#aria-checked
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price filter custom checkbox <div> |
aria-checked not defined — screen readers cannot announce whether the Price filter checkbox is checked or unchecked |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size filter custom checkbox <div> |
aria-checked not defined — screen readers cannot announce whether the Size filter checkbox is checked or unchecked |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand filter custom checkbox <div> |
aria-checked not defined — screen readers cannot announce whether the Brand filter checkbox is checked or unchecked |
Rule: Modal dialogs must expose
role="dialog"(orrole="alertdialog") so assistive technologies announce and treat them as dialogs
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Cart drawer <div> |
role="dialog" removed — screen readers cannot identify the panel as a dialog and will not switch to dialog-reading mode |
Rule: Modal dialogs should set
aria-modal="true"to signal to screen readers that background content is inert
Impact: Serious
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Cart drawer <div> |
aria-modal="true" removed — screen readers may allow users to browse content behind the open modal |
Rule: All interactable elements and landmark regions must have an accessible name
Impact: Critical
WCAG: 1.3.1 (A), 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Cart drawer <div> |
aria-label="Shopping cart" removed — the modal has no accessible name; screen readers cannot announce what the dialog is about |
| 2 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Cart items <ul> |
aria-label="Cart items" removed — the list has no accessible name |
Rule: Icon-only and symbol buttons must have an accessible name via
aria-labelor equivalent
Impact: Critical
WCAG: 1.3.1 (A), 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Decrease quantity <button> per cart item |
aria-label removed — button renders only "−" symbol; screen readers cannot convey which item's quantity is being changed |
| 2 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Increase quantity <button> per cart item |
aria-label removed — button renders only "+" symbol; screen readers cannot convey which item's quantity is being changed |
| 3 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Remove item <button> per cart item |
aria-label removed — icon-only button; screen readers cannot identify which item is being removed |
Rule: Non-text content conveying information must have a text alternative
Impact: Serious
WCAG: 1.1.1 (A) — Non-text Content
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Quantity <span> per cart item |
aria-label (e.g. "Quantity: 2") removed — screen readers read the raw number without context |
Rule: When a modal dialog is open, keyboard focus must be trapped inside it
Impact: Critical
WCAG: 2.1.2 (A) — No Keyboard Trap (inverse: focus must stay inside the dialog)
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Cart drawer | Focus is not moved into the modal on open and is not trapped — keyboard users can Tab to elements behind the open modal |
Rule: Dialogs and overlays must be dismissible with the Escape key
Impact: Serious
WCAG: 2.1.1 (A) — Keyboard
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Cart Drawer) | src/components/CartModal.jsx |
Cart drawer | keydown Escape handler removed — keyboard users cannot close the cart modal with the Escape key |
Rule: All interactable elements must have an accessible name
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
<button class="icon-btn cart-btn"> |
aria-label removed — the cart-open button has no accessible name; screen readers cannot announce its purpose or the number of items in the cart |
Rule: Widget containers that group related controls should use an appropriate ARIA
roleand accessible label so assistive technologies can identify and announce the grouping
Impact: Serious
WCAG: 1.3.1 (A) — Info and Relationships; 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<div class="sort-dropdown"> |
role="group" and aria-label="Sort products" removed — the sort widget wrapper has no group semantics or accessible label; screen readers cannot identify it as a named grouping |
Rule: Buttons that open a popup or disclosure must expose
aria-expandedso assistive technologies can announce whether the content is shown or hidden
Impact: Serious
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<button class="sort-btn"> |
aria-expanded removed — screen readers cannot tell whether the sort options list is currently open or closed |
Rule: Buttons that open a listbox, menu, or other popup should declare
aria-haspopupso assistive technologies warn the user before activation
Impact: Moderate
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<button class="sort-btn"> |
aria-haspopup="listbox" removed — screen readers are not informed that activating this button opens a popup list |
Rule: Buttons that control another element should use
aria-controlsto programmatically associate them with the controlled element
Impact: Moderate
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<button class="sort-btn"> |
aria-controls removed — screen readers cannot navigate from the trigger button to the options list it controls |
Rule: Custom select/listbox widgets must expose
role="listbox"and an accessible label so assistive technologies identify the container as a list of selectable options
Impact: Critical
WCAG: 1.3.1 (A) — Info and Relationships; 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<ul class="sort-options"> |
role="listbox" and aria-label="Sort options" removed — screen readers cannot identify the dropdown list as a listbox widget |
Rule: Items inside a
listboxmust carryrole="option"andaria-selectedso assistive technologies can announce each choice and its selection state
Impact: Critical
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<li class="sort-option"> (each option) |
role="option" and aria-selected removed — screen readers cannot identify list items as selectable options or announce which one is currently selected |
Rule: All UI functionality must be operable via keyboard; interactive elements must be focusable and respond to keyboard events
Impact: Critical
WCAG: 2.1.1 (A) — Keyboard
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/pages/NewPage.jsx |
<li class="sort-option"> (each option) |
tabIndex={0} and onKeyDown handler removed — sort options cannot be focused or activated via keyboard; keyboard-only users cannot select a sort order |
Rule: Disclosure buttons must expose
aria-expandedso assistive technologies can announce whether the controlled section is open or collapsed
Impact: Serious
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price <button class="filter-group-header"> |
aria-expanded removed — screen readers cannot announce whether the Price filter section is expanded or collapsed |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size <button class="filter-group-header"> |
aria-expanded removed — screen readers cannot announce whether the Size filter section is expanded or collapsed |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand <button class="filter-group-header"> |
aria-expanded removed — screen readers cannot announce whether the Brand filter section is expanded or collapsed |
Rule: Disclosure buttons should use
aria-controlsto programmatically associate them with the region they show/hide
Impact: Moderate
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price <button class="filter-group-header"> |
aria-controls="filter-price" removed — no programmatic link between the Price toggle button and its controlled panel |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size <button class="filter-group-header"> |
aria-controls="filter-size" removed — no programmatic link between the Size toggle button and its controlled panel |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand <button class="filter-group-header"> |
aria-controls="filter-brand" removed — no programmatic link between the Brand toggle button and its controlled panel |
Rule: The
idattribute on a controlled panel is required foraria-controls/aria-ownsassociations; without it the relationship between the toggle button and its panel cannot be established
Impact: Moderate
WCAG: 4.1.2 (A) — Name, Role, Value
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price <ul class="filter-options"> |
id="filter-price" removed — the panel has no ID; the toggle button cannot reference it via aria-controls or aria-owns |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size <ul class="filter-options"> |
id="filter-size" removed — the panel has no ID; the toggle button cannot reference it via aria-controls or aria-owns |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand <ul class="filter-options"> |
id="filter-brand" removed — the panel has no ID; the toggle button cannot reference it via aria-controls or aria-owns |
Rule: Disclosures and expandable sections should be collapsible with the Escape key to give keyboard users a quick way to dismiss them
Impact: Serious
WCAG: 2.1.1 (A) — Keyboard
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Products Page | src/components/FilterSidebar.jsx |
Price filter disclosure | No keydown Escape handler — keyboard users cannot collapse the Price section with the Escape key |
| 2 | Products Page | src/components/FilterSidebar.jsx |
Size filter disclosure | No keydown Escape handler — keyboard users cannot collapse the Size section with the Escape key |
| 3 | Products Page | src/components/FilterSidebar.jsx |
Brand filter disclosure | No keydown Escape handler — keyboard users cannot collapse the Brand section with the Escape key |
Issues related to content order and focus sequence, intentionally introduced for demo/testing purposes. Each issue is marked in the source code with an A11Y-GEN3 comment on the relevant line.
Rule: Content must not require two-dimensional scrolling at 320 CSS pixels viewport width (equivalent to 400% zoom on a 1280px-wide screen); zooming must not cause content to be clipped or hidden
Impact: Serious
WCAG: 1.4.10 (AA) — Reflow
overflow-x: hidden is applied to the <body> element, which suppresses horizontal scrolling entirely. When the page is zoomed to 300% (or viewed on a narrow viewport), the navigation bar and other fixed-width content exceed the visible area but cannot be scrolled to — they are silently clipped and cut off. Users who rely on zoom (low-vision users) lose access to the main navigation links without any indication that content is missing.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages | src/components/App.css |
body |
overflow-x: hidden prevents horizontal scrolling — at 300% zoom the navigation menu and header content are clipped and inaccessible; users cannot scroll to reach the cut-off content |
Rule: Content that has a meaningful sequence must preserve that sequence in the DOM so screen readers announce it in the correct logical order
Impact: Serious
WCAG: 1.3.2 (A) — Meaningful Sequence
The DOM order of elements does not match the visual order. CSS (flex-direction: column-reverse) is used to make the layout appear correct visually, but screen readers follow DOM order — causing them to announce content in a sequence that does not match the visual reading flow.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Homepage | src/components/FeaturedPair.jsx |
.featured-card (both cards) |
<div class="featured-card-image"> is placed before <div class="featured-card-info"> in the DOM. flex-direction: column-reverse in FeaturedPair.css restores the correct visual order (text on top, image on bottom), but screen readers announce the image alt text before the eyebrow, heading, and CTA link — the reverse of the meaningful sequence |
Rule: Heading levels must convey a logical document structure — levels should not be skipped or used out of order
Impact: Moderate
WCAG: 1.3.1 (A) — Info and Relationships
Heading elements have been deliberately set to wrong levels to break the document outline. CSS class-based styles preserve the visual appearance, but the semantic heading hierarchy is incorrect — screen readers and assistive technologies rely on heading levels to understand page structure and enable navigation.
| # | Page | File | Element | Wrong Level | Correct Level | Issue |
|---|---|---|---|---|---|---|
| 1 | Homepage | src/components/HeroBanner.jsx |
"Winter Basics" | <h3> |
<h1> |
Page-level heading uses h3, skipping h1 |
| 2 | Homepage | src/components/FeaturedPair.jsx |
Item titles | <h1> |
<h3> |
Card headings use h1, jumping above section level |
| 3 | Homepage | src/components/PopularSection.jsx |
"Popular on the Merch Shop" | <h4> |
<h2> |
Section heading uses h4, skipping h2 and h3 |
| 4 | Homepage | src/components/PopularSection.jsx |
Product card titles | <h1> |
<h3> |
Card headings use h1, jumping above section level |
| 5 | Homepage | src/components/TrendingCollections.jsx |
"Shop Trending Collections" | <h4> |
<h2> |
Section heading uses h4, skipping h2 and h3 |
| 6 | Homepage | src/components/TrendingCollections.jsx |
Collection card titles | <h1> |
<h3> |
Card headings use h1, jumping above section level |
| 7 | Homepage | src/components/TheDrop.jsx |
"The Drop" | <h4> |
<h2> |
Section heading uses h4, skipping h2 and h3 |
| 8 | Products Page | src/pages/NewPage.jsx |
Page title | <h3> |
<h1> |
Page-level heading uses h3, skipping h1 |
| 9 | Product Detail | src/pages/ProductPage.jsx |
Product name | <h3> |
<h1> |
Page-level heading uses h3, skipping h1 |
| 10 | Checkout | src/pages/CheckoutPage.jsx |
"Shopping Cart" / "Shipping & Payment" | <h3> |
<h1> |
Page-level headings use h3, skipping h1 |
| 11 | Checkout | src/pages/CheckoutPage.jsx |
"Order Summary" (×2) | <h5> |
<h2> |
Section headings use h5, skipping multiple levels |
| 12 | Order Confirmation | src/pages/OrderConfirmationPage.jsx |
"Thank you!" | <h3> |
<h1> |
Page-level heading uses h3, skipping h1 |
| 13 | All pages (Cart drawer) | src/components/CartModal.jsx |
"Shopping Cart" drawer title | <h5> |
<h2> |
Drawer heading uses h5, skipping multiple levels |
| 14 | All pages (Wishlist drawer) | src/components/WishlistModal.jsx |
"Wishlist" drawer title | <h5> |
<h2> |
Drawer heading uses h5, skipping multiple levels |
Rule: Accessible names must be meaningful and uniquely identify the purpose of an element — generic, vague, or context-free labels fail to convey intent to screen reader users
Impact: Serious
WCAG: 2.4.6 (AA) — Headings and Labels; 2.4.9 (AAA) — Link Purpose (Link Only); 4.1.2 (A) — Name, Role, Value
aria-label values have been replaced with generic, context-free strings. Visually the UI is unchanged, but screen reader users hear labels like "Minus", "Plus", "Click here", or "Product item" with no way to determine which product, which item, or what action is involved.
| # | Page | File | Element | Bad Label | Meaningful Label It Replaced | Issue |
|---|---|---|---|---|---|---|
| 1 | Products Page | src/components/ProductCard.jsx |
<article> product card |
"Product item" |
Product name (e.g. "Google Super G Trucker Hat") |
Every product card announces identically — screen readers cannot distinguish between products in the grid |
| 2 | Product Detail | src/pages/ProductPage.jsx |
"ADD TO CART" <button> |
"Add to cart" |
"Add [product name] to cart" |
No product name — when multiple pages are open or announced out of context, the button purpose is ambiguous |
| 3 | Product Detail | src/pages/ProductPage.jsx |
Wishlist <button> |
"Wishlist action" |
"Add [product name] to wishlist" / "Remove [product name] from wishlist" |
Neither the product nor the current state (add vs. remove) is conveyed |
| 4 | Checkout | src/pages/CheckoutPage.jsx |
Decrease quantity <button> (per cart item) |
"Minus" |
"Decrease quantity of [item name]" |
No item context — screen readers cannot tell which item's quantity is being changed |
| 5 | Checkout | src/pages/CheckoutPage.jsx |
Quantity <span> (per cart item) |
"Number" |
"Quantity: [n]" |
Raw number with no semantic context — screen readers cannot convey what the number represents |
| 6 | Checkout | src/pages/CheckoutPage.jsx |
Increase quantity <button> (per cart item) |
"Plus" |
"Increase quantity of [item name]" |
No item context — screen readers cannot tell which item's quantity is being changed |
| 7 | Checkout | src/pages/CheckoutPage.jsx |
Remove item <button> (per cart item) |
"Delete" |
"Remove [item name] from cart" |
No item context — screen readers cannot tell which item is being removed |
| 8 | All pages (Wishlist drawer) | src/components/WishlistModal.jsx |
Product image <a> link (per wishlist item) |
"Click here" |
"View [item name]" |
Non-descriptive link text — screen readers announce "Click here" with no indication of destination or product |
Rule: If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components must receive focus in an order that preserves meaning and operability
Impact: Serious
WCAG: 2.4.3 (A) — Focus Order
Explicit tabIndex values are used to force keyboard tab order into the reverse of the visual left-to-right sequence. Sighted users read the navigation as New → Apparel → Lifestyle → Stationery → Collections → Shop by Brand → Sale, but keyboard users tab through it as Sale → Shop by Brand → Collections → Stationery → Lifestyle → Apparel → New.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
Main navigation <nav> links |
tabIndex values are set in descending order (navItems.length - index) on each nav <Link>, reversing the tab sequence relative to the visual left-to-right order — keyboard focus order does not match the visual/logical reading order |
Issues that are not detectable by automated accessibility scanners (axe-core, Evinced engine) because the DOM is structurally valid — the problem is a missing dynamic announcement pattern. Each issue is marked in the source code with an A11Y-UNDETECTABLE comment on the relevant line.
Rule: Dynamic content changes that convey status, feedback, or errors must be announced to screen reader users via an ARIA live region (
role="alert",role="status",aria-live="polite", oraria-live="assertive")
Impact: Critical
WCAG: 4.1.3 (AA) — Status Messages; 1.3.1 (A) — Info and Relationships
References:
When the user submits the Shipping & Payment form with missing fields, up to 5 inline error messages are injected into the DOM. Each error <span> previously carried role="alert" (an implicit aria-live="assertive" live region), which caused screen readers to immediately announce the error text. With role="alert" removed, the errors appear visually but are completely silent — screen reader users receive no indication that the form submission failed or which fields are invalid. They must manually navigate the entire form to discover the errors.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | Checkout | src/pages/CheckoutPage.jsx |
<span id="firstName-error" class="form-error"> |
role="alert" removed — error appears visually but is not announced by screen readers |
| 2 | Checkout | src/pages/CheckoutPage.jsx |
<span id="lastName-error" class="form-error"> |
role="alert" removed — error appears visually but is not announced by screen readers |
| 3 | Checkout | src/pages/CheckoutPage.jsx |
<span id="address-error" class="form-error"> |
role="alert" removed — error appears visually but is not announced by screen readers |
| 4 | Checkout | src/pages/CheckoutPage.jsx |
<span id="cardNumber-error" class="form-error"> |
role="alert" removed — error appears visually but is not announced by screen readers |
| 5 | Checkout | src/pages/CheckoutPage.jsx |
<span id="expirationDate-error" class="form-error"> |
role="alert" removed — error appears visually but is not announced by screen readers |
When a product is added to the cart, the cart icon badge in the header updates its count (e.g. "0" → "1"). The badge <span> carries aria-hidden="true" (so the raw number is not read redundantly), but there is no aria-live region anywhere in the component to announce the change. Screen reader users receive no feedback that the add-to-cart action succeeded or that the cart now contains items.
| # | Page | File | Element | Issue |
|---|---|---|---|---|
| 1 | All pages (Header) | src/components/Header.jsx |
<span class="cart-count" aria-hidden="true"> |
No aria-live region exists to announce cart count changes — screen reader users receive no feedback when an item is added to the cart |