What a Baclion audit report looks like

Client reports stay confidential, so this one is written against our reference storefront, a store built from the checkout, search, and account patterns we audit most. It shows the structure of every report we deliver and seven findings exactly as your developers would receive them.

A gloved hand points a pencil at a light box showing a website wireframe drawn as an X-ray, with two hairline fractures circled in yellow.

Why a reference storefront

We never publish a client's findings. The storefront in this report is built from the components we test most often in e-commerce audits: a filtered category page, a cart drawer, a multi-step checkout, and account sign-in. Every finding, code sample, and fix follows the format of a real engagement; no organization's site or data appears here.

Six parts, written for two readers

Every report serves the person approving the budget and the developer making the change. Each part is written for one of them, and some for both.

Executive summary

One page in plain English: what we tested, who is affected and how, the few things to fix first, and what it will take.

Scope and sample

The templates, journeys, platforms, and versions tested, and why they represent the rest of the site.

Method

The assistive technology, browsers, and settings used, the WCAG version and level, and the dates of testing.

Findings register

Every issue with an ID, severity, success criterion, location, affected users, evidence, and fix, delivered in your tracker or a spreadsheet.

Component view

Findings grouped by the shared component that causes them, so you can see that fixing the header once clears the same issue on every page.

Roadmap

Fixes sequenced into sprints by severity, dependency, and effort, with a retest point after each.

The storefront in this sample

The sample audit covers eight templates of a mid-sized online store: home, search results, category with filters, product detail, cart drawer, checkout (address, delivery, and payment), order confirmation, and account sign-in. Two critical journeys were tested end to end: search to purchase as a guest, and sign-in to reorder.

Testing used NVDA with Firefox and Chrome and JAWS with Chrome on Windows, VoiceOver with Safari on macOS and iOS, TalkBack with Chrome on Android, a keyboard alone, browser zoom at 200% and 400%, and Windows contrast themes. The audit logged 41 findings. Each one below shows the fields from the register; in a real report, each also links to screenshots and, for blockers, a short screen recording.

An executive summary someone can read in two minutes

Here is the opening of the sample executive summary, as a client would receive it.

What we found. A customer who uses a keyboard instead of a mouse cannot finish checkout: the delivery-date picker traps them, and there is no way past it. Customers who use screen readers can add items to the cart but aren't told it worked, and the cart, search, and account buttons in the header have no names. These problems stop purchases outright and should be fixed first.

What it will take. Many of the 41 findings trace back to a handful of shared components: the header, the cart drawer, the form field, and the date picker. Fixing each of those once resolves the issue everywhere it appears. We estimate four two-week sprints for a team of two front-end developers.

What we recommend. Fix the checkout blockers in the first sprint, ship the component fixes over the next two, and add automated checks to the build so the same patterns don't come back.

Seven findings from the register

Seven of the 41 findings, chosen to show the range: a blocker, several serious issues, and two of the criteria added in WCAG 2.2. Severity follows our severity model.

Blocker

BAC-003 · Delivery-date picker traps keyboard focus

Criterion
SC 2.1.2 No Keyboard Trap (A)
Where
Checkout › Delivery › Date picker
Who is affected
Keyboard users, switch users, and screen reader users

What we observed. Using a keyboard alone in Chrome and Firefox on Windows, pressing Enter on Choose a delivery date opens the calendar and moves focus to the first available day. From there, Tab, Shift+Tab, and Escape all leave focus inside the grid. There is no close button, and the arrow keys cycle through dates indefinitely. The only way out is a mouse click outside the calendar, so a keyboard user can't reach the payment step. NVDA also announces each day as "button" with no date; that is logged separately as BAC-004.

Fix. Stop swallowing every keypress. Arrow keys should move between days, Tab should reach the dialog's Cancel and Choose date buttons, and Escape should close the calendar and return focus to the trigger. The ARIA Authoring Practices date picker dialog describes the full keyboard model. If the calendar is a native <dialog> opened with showModal(), the browser handles Escape for you.

// Before: every key is blocked, so nothing can leave the grid
grid.addEventListener("keydown", (e) => {
  e.preventDefault();
  moveFocus(e.key);
});

// After: arrows move between days, Tab works normally,
// and Escape closes the dialog and returns focus to the trigger
grid.addEventListener("keydown", (e) => {
  if (e.key === "Escape") {
    closeDialog();
    trigger.focus();
  } else if (e.key.startsWith("Arrow")) {
    e.preventDefault();
    moveFocus(e.key);
  }
});
Serious

BAC-007 · Cart drawer opens silently and leaves focus behind it

Criterion
SC 2.4.3 Focus Order (A); SC 4.1.2 Name, Role, Value (A)
Where
Product detail › Add to cart › Cart drawer
Who is affected
Screen reader users, keyboard users, and people using screen magnification

What we observed. With NVDA and Firefox, activating Add to cart produces no announcement. The drawer slides in from the right, but focus stays on the button underneath it. Tabbing continues through the product page behind the drawer, and the drawer's contents are reached only after every link in the footer. VoiceOver on iOS behaves the same way when swiping. The drawer is a div with no role or accessible name, so even when users reach it, nothing identifies it as the cart. At 400% zoom, the drawer fills the viewport while the focus indicator sits hidden behind it.

Fix. Treat the drawer as a modal dialog. Give it a role and a name, move focus into it when it opens, keep the page behind it inert, and return focus to the trigger when it closes. A native <dialog> opened with showModal() provides the role and the inert background.

<!-- Before -->
<div class="cart-drawer is-open">
  <div class="cart-drawer__title">Your cart</div>
  …
</div>

<!-- After -->
<dialog class="cart-drawer" aria-labelledby="cart-title">
  <h2 id="cart-title" tabindex="-1">Your cart, 2 items</h2>
  …
  <button type="button" class="cart-drawer__close">Close cart</button>
</dialog>
addToCart.addEventListener("click", async () => {
  await addItem(variantId);
  drawer.showModal();
  drawer.querySelector("#cart-title").focus();
});
drawer.addEventListener("close", () => addToCart.focus());
Serious

BAC-011 · Icon-only header buttons have no accessible name

Criterion
SC 4.1.2 Name, Role, Value (A); SC 1.1.1 Non-text Content (A)
Where
Global header › Search, account, and cart buttons (all templates)
Who is affected
Screen reader users and speech input users

What we observed. NVDA reads the three header controls as "button", "button", and "button, 2". VoiceOver on macOS lists three unnamed buttons in the rotor. With Voice Control on iOS, saying "Tap cart" does nothing, so users have to fall back to numbered overlays. Each button contains only an inline SVG with no title or label. Because the header is shared, the issue appears on every page; the component view counts it once.

Fix. Give each button a text name and hide the decorative icon. Visually hidden text works reliably across screen readers and browser translation tools.

<!-- Before -->
<button class="header-icon" data-action="cart">
  <svg viewBox="0 0 24 24">…</svg>
  <span class="badge">2</span>
</button>

<!-- After: announced as "Cart, 2 items" -->
<button class="header-icon" data-action="cart">
  <svg viewBox="0 0 24 24" aria-hidden="true">…</svg>
  <span class="visually-hidden">Cart,</span>
  <span class="badge">2</span>
  <span class="visually-hidden">items</span>
</button>
Moderate

BAC-019 · Sticky header hides the focused field

Criterion
SC 2.4.11 Focus Not Obscured (Minimum) (AA, new in WCAG 2.2)
Where
Checkout › Address form
Who is affected
Keyboard users and people using screen magnification

What we observed. At a 1280 by 800 window, the sticky header and the order-summary bar together cover the top 140 CSS pixels of the viewport. Tabbing backward with Shift+Tab, the browser scrolls each field just far enough to reach the top edge, which is underneath those bars. Address line 2 and City are completely hidden, focus ring included, when they receive focus. At 200% zoom, five of the eleven fields are fully obscured.

Fix. Tell the browser how much of the viewport the sticky elements occupy, so it scrolls focused elements clear of them. If the header height changes, set the value from a custom property updated by a ResizeObserver.

/* Before: no allowance for sticky elements */
html {
  scroll-behavior: smooth;
}

/* After */
html {
  scroll-padding-top: 9rem; /* header + order summary */
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
Serious

BAC-022 · Checkout errors aren't connected to their fields

Criterion
SC 3.3.1 Error Identification (A); SC 1.3.1 Info and Relationships (A)
Where
Checkout › Address and payment forms
Who is affected
Screen reader users; people with low vision or cognitive disabilities

What we observed. Submitting the address form with an invalid ZIP code shows red text, "Enter a valid ZIP code", below the field and turns its border red. With JAWS and Chrome, nothing is announced on submit, and focus stays on Continue to delivery. Moving back to the ZIP field, JAWS reads "ZIP code, edit" with no mention of the error. NVDA behaves the same way. A screen reader user hears the page do nothing and has no way to learn why.

Fix. Associate each message with its field, mark the field invalid, and move focus to the first field with an error, or to an error summary at the top of the form. The rewritten message also says how to fix the problem, which meets SC 3.3.3 Error Suggestion (AA).

<!-- Before -->
<label for="zip">ZIP code</label>
<input id="zip" name="zip" class="is-error">
<div class="error-text">Enter a valid ZIP code</div>

<!-- After -->
<label for="zip">ZIP code</label>
<input id="zip" name="zip" autocomplete="postal-code"
       aria-invalid="true" aria-describedby="zip-error">
<p id="zip-error" class="error-text">
  Enter a 5-digit ZIP code, such as 42103.
</p>
Moderate
Criterion
SC 2.2.2 Pause, Stop, Hide (A)
Where
Home › Hero carousel
Who is affected
People with attention-related or cognitive disabilities, screen reader users, and people using magnification

What we observed. The carousel advances every five seconds and loops indefinitely. There is no pause control, and rotation doesn't stop on hover or keyboard focus. The slide container is an aria-live="polite" region, so with NVDA each new slide interrupts whatever the user is reading. At 400% zoom, a reader working through the first slide's offer terms loses them mid-sentence when the slide changes.

Fix. Add a visible pause button as the first control in the carousel, stop rotation on hover and focus, and turn off the live region while slides rotate on their own. Don't start rotation at all when the visitor has asked for reduced motion. Better still, don't auto-advance.

<!-- Before -->
<div class="hero-carousel" aria-live="polite">…</div>

<!-- After -->
<section class="hero-carousel" aria-roledescription="carousel"
         aria-label="Featured offers">
  <button type="button" class="carousel-pause" aria-pressed="false">
    Pause slideshow
  </button>
  <div class="slides" aria-live="off">…</div>
</section>
pauseButton.addEventListener("click", () => {
  const paused = pauseButton.getAttribute("aria-pressed") === "true";
  pauseButton.setAttribute("aria-pressed", String(!paused));
  paused ? startRotation() : stopRotation();
});
if (matchMedia("(prefers-reduced-motion: reduce)").matches) stopRotation();
Moderate

BAC-031 · Quantity stepper buttons are too small to hit reliably

Criterion
SC 2.5.8 Target Size (Minimum) (AA, new in WCAG 2.2)
Where
Cart drawer and cart page › Quantity stepper
Who is affected
People with tremor or limited dexterity, and anyone on a small touchscreen

What we observed. The minus and plus buttons measure 16 by 16 CSS pixels and sit 2 pixels from the quantity field. That is below the 24 by 24 CSS pixels minimum, without enough spacing to qualify for the spacing exception. On an iPhone, our analyst removed an item several times while trying to increase its quantity. With Switch Control on iOS, the scanning highlight on the two buttons is hard to tell apart.

Fix. Enlarge the hit area without changing the visual design, and add space between the targets. We recommend 44 by 44 CSS pixels, the Level AAA size in SC 2.5.5, because this stepper sits in a dense layout used mostly on phones.

/* Before */
.qty-stepper button {
  width: 16px;
  height: 16px;
  margin: 0 2px;
}

/* After: 44px hit area, same 16px icon */
.qty-stepper button {
  min-width: 44px;
  min-height: 44px;
  display: inline-grid;
  place-items: center;
}
.qty-stepper button svg {
  width: 16px;
  height: 16px;
}

From findings to roadmap

The roadmap turns the register into sprints. This is the sample's plan for two front-end developers working in two-week sprints, with blockers first and shared components next.

Sample remediation roadmap, sequenced by severity and shared components
SprintWhat shipsFindings closedRetest
Sprint 1Date picker keyboard model; checkout error handling and focus managementBAC-003, BAC-004, BAC-022, and three related form findingsKeyboard and JAWS retest of checkout
Sprint 2Cart drawer as a modal dialog; named header buttons; larger quantity steppersBAC-007, BAC-011, BAC-031, and nine findings in the same shared componentsNVDA, VoiceOver on iOS, and TalkBack retest of search to purchase
Sprint 3Scroll padding for sticky elements; carousel pause and reduced-motion support; remaining moderate findingsBAC-019, BAC-027, and eleven moderate findingsZoom, reflow, and contrast-theme retest of all eight templates
Sprint 4Minor findings, content fixes such as alt text and link text, and automated checks added to CIThe remaining ten findingsFull regression retest; register updated and ready for an ACR
OngoingBaclion Watch weekly scans and quarterly manual checks on the eight templatesRegressions caught before they reach customersQuarterly

Questions about the report

Is this a real client's report?

No. Client reports are confidential. This one is written against our reference storefront, so you can see the full format, the level of detail, and the kind of fixes we recommend without anyone's findings being disclosed.

What format does the real report come in?

The executive summary, scope, method, and roadmap arrive as an accessible PDF or web report. The findings register goes into your issue tracker or a spreadsheet, whichever your team works from.

Is the report itself accessible?

Yes. Reports are delivered as tagged PDFs or HTML and checked with the same tools and screen readers we use on your site.

Can we share the report with procurement or our attorney?

Yes, it is yours. The executive summary and method work well for procurement reviewers, and the full register gives counsel the technical detail. If you need a formal conformance document, we can turn the audit into an Accessibility Conformance Report.

Tell us what your website needs to do.

Send a link and a sentence about your deadline. You will hear back from a person within one business day, usually with a short list of questions and a proposed scope.