Marketing pages get audited because they are easy to audit. Checkout gets sued because checkout is where the harm is.
A person who cannot read your About page has been inconvenienced. A person who cannot complete a purchase has been denied a service, which is the shape of harm that accessibility law is actually written around. That is a large part of why ecommerce accounts for the majority of digital accessibility claims in the US, and why EU market surveillance under the Accessibility Act went straight at retail platforms.
So here is the purchase flow, step by step, with what I find broken at each stage. Almost none of this shows up in an automated scan.
Step 1: The variant selector
Size, colour, capacity. Almost always built as styled divs with click handlers, because a real radio group is harder to make look like a row of swatches.
The failures cluster. The options are not reachable by keyboard at all. Or they are reachable but Enter and Space do nothing because only click was bound. Or selection state is communicated purely by a border colour, so a screen reader user has no idea which size is currently chosen. Or out of stock variants are visually greyed but remain focusable and selectable with no announcement of why nothing happens.
A native fieldset with radios solves all of it and can be styled to look like anything.
<fieldset>
<legend>Size</legend>
<input type="radio" id="s-m" name="size" value="m">
<label for="s-m">Medium</label>
<input type="radio" id="s-l" name="size" value="l" disabled>
<label for="s-l">Large, out of stock</label>
</fieldset>
Note the out of stock state living in the label text, not only in a colour. If your design refuses a visible sentence there, put it in visually hidden text inside the label.
Step 2: Add to cart, and the mini cart that appears
The user activates the button. A drawer slides in from the right. Visually this is obvious. Non visually, three things usually go wrong at once.
- Nothing is announced. The item was added, the total changed, and no status region said so.
- Focus stays behind. The drawer is open on screen but the keyboard is still on the product page, so tabbing moves through content the user cannot see.
- Focus is not contained. Tab keeps going past the drawer’s last control and into the page underneath, which is now inert to the eye but not to the keyboard.
If the drawer is genuinely modal, the modern answer is short. Use a real dialog element, move focus into it, and let the browser handle the trap and the inertness of the rest of the page.
const dialog = document.querySelector("#cart-drawer");
dialog.showModal(); // traps focus, makes background inert
dialog.querySelector("h2").focus();
// on close, return focus to what opened it
dialog.addEventListener("close", () => addToCartButton.focus());
That last line is the one people forget. Closing a dialog without restoring focus dumps a keyboard user back at the top of the document, and they have to navigate the entire page again to get where they were.
Step 3: The quantity stepper
Small component, disproportionate failure rate. Two icon buttons with no accessible name, a number that updates with no announcement, and frequently no way to type a value directly.
Give the buttons real names that include the product, because a page with eight line items otherwise announces increase, increase, increase, and gives no way to tell which row you are on.
<button aria-label="Decrease quantity of Blue Runner, size 42">−</button>
<input type="number" aria-label="Quantity of Blue Runner, size 42" value="1" min="1">
<button aria-label="Increase quantity of Blue Runner, size 42">+</button>
<p role="status" class="visually-hidden">Quantity updated to 2. Subtotal 180,000 COP.</p>
Debounce that status message. Firing it on every keystroke of a typed quantity produces a stream of interruptions.
Step 4: The multi step form
Shipping, then delivery method, then payment. Each step usually replaces the previous one in place without a URL change, which means the browser never treats it as a navigation and assistive technology is never told anything happened.
Three things fix most of it. Move focus to the new step’s heading when it renders. Give the heading a tabindex of -1 so it can receive focus programmatically without entering the tab order. And expose progress in text rather than only as a coloured progress bar, so step 2 of 3 is something a person can actually perceive.
While you are in the form, add autocomplete tokens to every field. Name, address line 1, postal code, country, telephone, email, credit card fields. This is its own WCAG criterion, it is trivial, and it measurably reduces abandonment for everybody, not just users with disabilities.
Step 5: Validation errors
This is the single highest value fix in checkout and the one most often missing.
Typical broken behaviour: the user submits, three fields turn red somewhere up the page, focus stays on the submit button, and nothing is announced. From a non visual perspective the button simply stopped working.
What to build instead is an error summary at the top of the form, focused on submit, listing each problem as a link to the offending field.
<div role="alert" tabindex="-1" ref={summaryRef}>
<h2>There are 2 problems with your details</h2>
<ul>
<li><a href="#postal">Enter a postal code</a></li>
<li><a href="#card">Card number must be 16 digits</a></li>
</ul>
</div>
Then wire each field to its own message with aria-describedby and mark it aria-invalid. Write messages that say what to do, not what went wrong. Enter a postal code beats This field is invalid, for every user on every device.
Step 6: The payment iframe
The part you did not write and cannot fix, which does not remove it from your conformance scope.
Test your provider’s hosted fields with a keyboard and a screen reader before you commit to them. Check that the iframe carries a title attribute, that focus enters and leaves it cleanly, and that errors raised inside it reach your outer error summary rather than dying silently in a frame the user’s software is not currently reading.
If the provider fails, that is procurement evidence. Vendors respond to lost deals faster than to bug reports.
The one nobody remembers: session timeouts
Checkouts expire. Under WCAG, a time limit generally requires warning the user before it runs out and offering a way to extend it, with narrow exceptions.
A silent twenty minute expiry disproportionately affects users who are slower to complete forms, which includes people using switch access, voice input, or a screen reader. Losing a full cart to an invisible clock is not an edge case, it is a designed in failure.
The twenty minute test
You do not need tooling to find most of this. Unplug the mouse. Starting from a category page, select a product, choose a variant, add it to the cart, open the cart, change the quantity, proceed to checkout, submit the form with a deliberate error, correct it, and reach payment.
If you can do that without touching the mouse and without ever losing track of where focus is, you are ahead of most storefronts on the web. If you get stuck, you just found the thing that would have been in the complaint.
I audit and remediate ecommerce checkouts against WCAG 2.1 and 2.2 AA, including VTEX and headless storefronts. Write to me at serbeldiaz@gmail.com.

No responses yet