Screen readers, voice control and AI browsers all use the page's accessibility tree: the roles, names and states of its elements. A storefront that gets the tree right works for shoppers with assistive technology and for agents that shop on a person's behalf. This page lists the patterns a storefront most often gets wrong, and the native HTML that fixes each.
An AI browser or browser agent doesn't see your CSS. It reads the accessibility tree, decides which control does what from its role and name, and acts on it. A cart drawer that is hidden but still focusable, a quantity shown as plain text between two "−" and "+" buttons, or an error that appears without being announced confuses an agent in the same way it confuses a screen reader user. Fixing these is the same work.
Prefer native HTML. It brings the right role, name, keyboard behaviour and state with it, and it works without JavaScript.
Cart drawers, filter panels and assistants are dialogs. Use <dialog> and open it with showModal():
<button type="button" onclick="document.getElementById('cart').showModal()">Basket (2)</button>
<dialog id="cart" aria-labelledby="cart-title" closedby="any">
<h2 id="cart-title">Your basket</h2>
<!-- lines -->
<button type="button" autofocus onclick="this.closest('dialog').close()">Close</button>
</dialog><button type="button" onclick="document.getElementById('cart').showModal()">Basket (2)</button>
<dialog id="cart" aria-labelledby="cart-title" closedby="any">
<h2 id="cart-title">Your basket</h2>
<!-- lines -->
<button type="button" autofocus onclick="this.closest('dialog').close()">Close</button>
</dialog>showModal() makes the rest of the page inert, exposes the drawer as a modal dialog, moves focus into it, closes it on Escape and returns focus when it closes. Don't add role="dialog", a focus trap or an Escape handler on top. closedby="any" adds light dismiss (a click outside).
<dialog> is not.overscroll-behavior: contain on the open dialog and its backdrop.<main> as the first focusable element. A storefront header and mega menu put dozens of links ahead of the content.<h1>.<nav aria-label="Breadcrumbs"> with an ordered list. Mark the last item aria-current="page" and draw the separators in CSS, not as text.<main>, labelled <nav> elements ("Shop", "Language"), a <footer>.<search> element, around the search form and around the facet sidebar.The focus indicator needs a contrast of at least 3:1 against the colours around it. Brand accent colours (light greens, yellows) often fail on white or off-white backgrounds. Use :focus-visible, and don't replace the outline on inputs with a thin border change.
<input type="number" name="quantity" min="1">, with the − and + buttons as helpers. An agent can then read and set the quantity in one step instead of clicking N times.<fieldset> with a <legend> ("Colour", "Version"), not clickable <div>s.role="alert" for errors, role="status" for progress), or move focus to the error summary.autocomplete="email", name, shipping street-address, postal-code, tel. Shoppers fill the form in one tap, and agents know what each field is.function onKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
if (e.key !== 'Enter' || e.shiftKey) return;
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
e.preventDefault();
send();
}function onKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
if (e.key !== 'Enter' || e.shiftKey) return;
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
e.preventDefault();
send();
}An on-site assistant streams its answer. A role="log" region is right for a chat, but don't let it announce every token: update the live region when a message is complete, or debounce it. Show "thinking" as role="status" text, not as an aria-label on an element without a role.
Respect prefers-reduced-motion per animation. Drop movement (a drawer sliding in, a scale on hover) and keep transitions that don't move things, such as a fade. A global rule that sets every animation and transition to 0.01 ms also removes feedback that helps.
Open the browser's accessibility tree view (in Chrome DevTools, the Accessibility pane) and tab through the page with the screen off. If you can't tell what a control does from its role and name, neither can an agent.