We Audited Provia's AI Shopping Chat. Here's What the Before Looks Like.


We pointed axe-core at an AI shopping chat called Provia last week. Five product cards rendered after we typed “show me hoodies.” Axe flagged three violations — one serious, two moderate — across six DOM nodes. The real story is what axe couldn’t see.

This is part 1 of two. Part 2 will publish after Provia’s founder ships the fixes he’s already working on, and it will show the diff against the exact same scan. Everything below was sent to him as a private report first. He read it, decided to let us name him, and told us to put the test store URL in the article too. That’s how this got here.

How this started

A couple of weeks ago we published a scan of 30 SaaS pricing pages. In the dev.to comments, a founder named Ali Afana asked whether we’d ever run the same pass against AI product interfaces. We said yes, and that cohort scan went out a few days later. Ali replied again — this time mentioning that he was building Provia, an AI shopping chat for Shopify stores, and wondering out loud what axe would say about his own surface.

We offered to run it privately first, send him the raw findings, and only go public if he wanted to. He said yes to all three, and a week later he came back with: publish part 1 now, I’ll ship the fixes, then publish part 2 with the after-diff. Un-redact the store name, it’s a test store I spun up, not a real merchant. Name Provia.

So here we are. This is part 1.

What Provia is

Provia is an AI chat that a Shopify store owner embeds on their storefront. A shopper types what they’re looking for — “show me hoodies”, “something under fifty bucks for my nephew”, that kind of thing — and the chat replies conversationally with product cards rendered inline in the transcript. It’s the same interaction model a lot of ecommerce teams are quietly building right now: assistant on the left, results inline, no separate search page. The store we audited is a test store Ali created called louna, at the URL https://provia-saas.vercel.app/stores/d288d7d5-8d31-4d3d-b976-c19779f4ba65/chat. Hoodies, caps, a few casual pieces. It’s a toy storefront, but the chat code is the production code.

How we ran the audit

axe-core 4.11, headless Chromium via our Chrome-for-Testing instance, rule tags wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa, and best-practice. We disabled color-contrast for this run — axe’s contrast check is unreliable in headless mode and we didn’t want to ship noisy numbers. Three scans in total: one at the “enter your name” gate, one at the empty chat view right after, and one after we typed show me hoodies and waited for the assistant to respond. First product card came back in about twelve seconds. Five cards in the final render.

The same workflow we use on every cohort, pointed at a single real AI surface instead of thirty at once.

The top-line numbers

Three axe-core violations with cards on screen, six DOM nodes total. One serious, two moderate. Three focusables in the tab order across the whole chat view: a “Back” link, the chat input, and a “Send” button. The <html lang="en"> attribute is set. The <h1> is present and correct — it reads louna, the store name. There is no <main> landmark anywhere on the page. There are no live regions. Five product cards rendered as naked <div>/<span> trees with inline styles, zero list semantics.

Worth sitting with that focusable count for a second. Three. On a chat surface whose entire purpose is to show you products you might want to buy, exactly zero of those products are reachable with the Tab key.

The worst issue: a scrollable rail the keyboard can’t touch

The single serious violation in the scan is scrollable-region-focusable, and it hits the card rail and the message log area both. Axe’s rule here is simple: if a container has overflow: auto and isn’t itself focusable, and doesn’t contain any focusable children, then a user who can’t use a mouse or trackpad has no way to scroll it. WCAG 2.1.1 Keyboard, serious impact.

The card rail in Provia is a plain <div> with display: flex; overflow-x: auto. No tabindex. The cards inside it are also plain <div>s. No tabindex, no <button>, no <a>. A keyboard-only shopper who asks for hoodies literally cannot scroll the results. Tab jumps straight from the input back up to the Back link and there is no fourth stop.

This is the one we’d call a shipping-blocker for keyboard-only users, and it’s also the one Ali told us he’s fixing first.

What the cards actually look like

Here’s a slice of the raw outerHTML we pulled out of the DOM, verbatim, nothing edited except trimming the inline style strings for readability:

<div style="display: flex; gap: 12px; overflow-x: auto; ...">
  <div style="min-width: 180px; max-width: 200px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; ...">
    <div style="width: 100%; height: 140px; overflow: hidden; ...">
      <img alt="Ultimate Comfort Hoodie" src="https://.../1775486091591.jpg" style="...">
    </div>
    <div style="padding: 12px 14px;">
      <p style="font-size: 13px; font-weight: 600; ...">Ultimate Comfort Hoodie</p>
      <p style="font-size: 11px; ...">Stay cozy and stylish with this classic grey hoodie, perfect for any casual outing.</p>
      <div style="display: flex; justify-content: space-between; align-items: center;">
        <span style="font-size: 15px; font-weight: 700; ...">$45</span>
        <span style="font-size: 10px; ...">Hoodies</span>
      </div>
    </div>
  </div>
  <!-- ...four more identical card divs... -->
</div>

No role. No aria-label. No <ul>, no <li>, no role="list", no role="listitem". Five sibling divs, each one a wrapper around an image, a name, a description, a price, and a category tag. It looks fine on screen because the CSS does all the visual work. The structure only matters to the assistive-tech layer, and at the assistive-tech layer the structure is not there.

A note before we quote the “screen reader output”

Everything we’re about to say about what a screen reader hears is a reconstruction from the computed accessibility tree we captured, not a real VoiceOver or NVDA recording. Real screen readers layer their own verbosity settings and announcement heuristics on top of the a11y tree, so a live VO capture would differ in the details. It would not differ in the underlying problem, because the problem comes from the DOM. If you want a real capture for the part 2 post we’ll run one against the deployed preview. With that caveat on the table, here’s what the accessibility tree produces when you walk through the five cards:

“Ultimate Comfort Hoodie, graphic. Ultimate Comfort Hoodie. Stay cozy and stylish with this classic grey hoodie, perfect for any casual outing. Forty-five dollars. Hoodies. Evergreen Comfort Hoodie, graphic. Evergreen Comfort Hoodie…”

Two things are happening there that a sighted shopper wouldn’t notice. The first is that the product name gets announced twice per card — once from the <img alt> and once from the <p> right below it. Not a severity issue, just a cadence issue. The second is bigger: there’s no “list, 5 items” on entry and no “card 2 of 5” marker between cards. The user lands in a flat stream of generic nodes and has to infer from repetition that they’re looking at products at all. Ask the assistant “three products, which is cheapest” and a sighted user scans the prices instantly. A screen reader user has to listen linearly through all five, tracking prices mentally, hoping they didn’t miss one while the next card started announcing.

What Provia got right

We want to be specific about this part, because “audit finds three violations” is the kind of framing that makes it sound like the whole surface is broken. It isn’t.

Every <img> in the card rail has a real alt attribute, and the alt text matches the product name on the card. That is not automatic and it is not universal. We scan a lot of AI surfaces, and empty alt attributes on product images are genuinely common — we’ve seen entire rails where every image announces as “graphic, graphic, graphic.” Provia’s images actually name themselves. It means the cards are never truly silent to a screen reader, even without any of the structural fixes we’re about to describe.

Beyond the alt text: <html lang> is set to en, which sounds trivial until you see how many sites skip it. The <title> element is populated. There’s a real <h1> with the store name in it. The name-entry gate before the chat has clean, focusable form controls. None of this earns an article on its own, but for context: our recent AI product landing pages scan looked at 29 sites in the same category and 100% of them failed WCAG 2.1 AA on the basics Provia is already clearing. What’s missing here is structure around that baseline, not the baseline itself.

The honest framing here is: this isn’t a “your site is broken” story. It’s a “your site has structural gaps that are all cheap to fix” story.

The missing input label

This one’s smaller than the scrollable rail but it’s the most visible to real users. The chat input field looks like this in the DOM:

<input type="text" placeholder="Type a message..." />

No <label>, no aria-label, no aria-labelledby. The placeholder is the only text that identifies what the field is for.

Placeholder text is not an accessible name. That’s not our opinion, it’s the W3C Accessible Name and Description Computation spec — placeholder is not in the name-calculation chain. In practice, VoiceOver lands on the field and announces “edit text, blank.” A screen reader user has to guess from context that this is the thing they type their question into. Ali told us in the reply thread that this one gets an aria-label added this week, which is maybe three characters of code change.

This is a category pattern, not a Provia pattern

The reason we keep scanning this stuff is that the same gaps show up everywhere once you start looking. When we ran axe against 29 AI product landing pages, all 29 failed — and the failure mode wasn’t malice or inattention, it was structure missing under a beautifully designed surface. The Provia chat is a more specific version of the same thing. Product cards get rendered from a tool-call handler that emits JSX without thinking about list semantics, because the design system never had a <ProductCard> primitive in the first place. Keyboard focus never got wired up, because in a month-one product there’s no keyboard-only user on the team yet to notice. Live regions get skipped, because “the assistant response just appears” is already working visually. Every one of these gaps is a hundred other AI chat products right now, not just this one.

We know because we run the same scanner against ourselves. When we audited our own blog a few weeks ago we found 27 violations on our own surface. We fixed them and published the diff. Nobody gets to skip this part, including us.

What Ali is fixing for part 2

The private report we sent Ali had six cheap fixes and one slightly bigger one. He’s committed to the two that matter most before we publish part 2: keyboard navigability for the scrollable card rail (the serious issue), and a real aria-label on the chat input. Anything else he picks up from the report’s fix list before he re-deploys is bonus.

When he’s done, we re-run the exact same scanner against the exact same URL with the exact same show me hoodies query, and we publish what the after-scan says. Same axe rules, same facts file format, same accessibility tree walk. No retrofitting, no “we also tried this other query.” Just the diff.

Part 2 lands after Ali ships

This is build-in-public the way it’s actually supposed to work. Real code, real findings, real founder response, real fixes incoming. Ali saw every sentence of this article’s source material in the private report before we published a word of it — the decision to go public with the store name and the company name was his, not ours, and he made it because he thinks other founders building AI surfaces will learn from watching the fix happen out loud.

We’ll publish the after-diff once he ships.

— A11yFix team