Accessible Storefronts: Meeting WCAG 2.1 in Practice
Accessible Storefronts: Meeting WCAG 2.1 in PracticeAugust 20, 2026

Accessible Storefronts: Meeting WCAG 2.1 in Practice

Semantic HTML, ARIA, and keyboard navigation on a real e-commerce rebuild — what we implemented, how we tested it, and why accessibility also improved mobile conversion.

Table of Contents

  1. Accessibility Is a Conversion Feature
  2. Start With Semantics, Not ARIA
  3. Keyboard and Focus
  4. Forms, Errors, and Live Regions
  5. Color, Motion, and Images
  6. How We Tested

Accessibility Is a Conversion Feature {#why}

On the medical storefront, accessibility work sat next to responsive layouts. Touch-friendly components and WCAG 2.1 basics shipped together. Mobile conversion rose about 22%. Some of that was layout. Some of that was the same discipline: larger targets, clearer labels, fewer traps.

WCAG 2.1 AA was the bar: perceivable, operable, understandable, robust. We did not chase AAA on marketing flourishes. We did not ship a checkout a keyboard user could not finish.

Start With Semantics, Not ARIA {#semantics}

ARIA is a patch when HTML is not enough. Most product UI never needs role="button" if you used <button>.

// Wrong: clickable div, no keyboard, no name
<div className="card" onClick={addToCart}>Add to cart</div>

// Right
<button type="button" onClick={addToCart}>
  Add to cart
</button>

Landmarks we required on every template:

  • header / nav with an accessible name
  • one main
  • footer
  • skip link to #main

Headings must nest. A product name as h1, variants as h2, not a soup of styled p tags.

<a href="#main" className="skip-link">
  Skip to content
</a>
<main id="main">...</main>

Keyboard and Focus {#keyboard}

If you can click it, you can Tab to it, activate it with Enter/Space, and see focus.

.btn:focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: 2px;
}

Do not outline: none without a replacement. Modals must:

  1. Move focus inside on open
  2. Trap Tab within the dialog
  3. Restore focus to the opener on close
  4. Close on Escape
useEffect(() => {
  if (!open) return;
  const previouslyFocused = document.activeElement as HTMLElement | null;
  closeRef.current?.focus();

  return () => previouslyFocused?.focus();
}, [open]);

Mega-menus and custom selects are where keyboard support usually dies. Prefer native <select> until the design truly needs a listbox.

Forms, Errors, and Live Regions {#forms}

Checkout is an a11y exam.

<label htmlFor="email">Email</label>
<input
  id="email"
  type="email"
  autoComplete="email"
  aria-invalid={Boolean(error)}
  aria-describedby={error ? 'email-error' : undefined}
/>
{error ? (
  <p id="email-error" role="alert">
    {error}
  </p>
) : null}
  • Every input has a visible label (placeholder is not a label)
  • Errors are text, not color alone
  • role="alert" or aria-live="polite" for submit failures
  • Do not steal focus on every keystroke; move focus to the first error on submit

Color, Motion, and Images {#visual}

  • Contrast: 4.5:1 for body text, 3:1 for large text and UI chrome
  • Error state is not red-only; include an icon and text
  • prefers-reduced-motion disables carousels auto-playing
  • Informative images have alt; product heroes describe the product, not "image1.png"
  • Decorative icons: alt="" or aria-hidden="true"
@media (prefers-reduced-motion: reduce) {
  .carousel {
    animation: none;
  }
}

How We Tested {#test}

  1. Keyboard-only pass of login, search, and checkout
  2. VoiceOver / NVDA on the same flows
  3. axe or Lighthouse a11y in CI on page templates
  4. Contrast checks in the design system, not per PR screenshot

Automated tools catch missing labels. They will not catch a focus trap. A human keyboard pass before release is still required.

Accessible HTML is not a compliance sticker. It is the same structure that makes SEO, mobile usability, and design QA easier. Build it into the component library and most pages inherit it.