For twenty years, "write semantic HTML" was advice people agreed with and then ignored. A <div onclick> worked fine for mouse users, Googlebot didn't care much, and the screen-reader users it failed were easy to forget.
That changed in 2026. Agentic browsers such as ChatGPT Atlas, Perplexity Comet and Chrome's auto-browse now act on pages for users: comparing products, filling forms, booking things. They have to decide what each element on your page is and what it does. The cheapest, most reliable signal they have is the same one a screen reader uses: the accessibility tree.
So the boring advice is now traffic advice. If an agent can't tell your "Book now" div is a button, it books somewhere else.
How agents see a page
Browser agents mix three inputs:
-
The accessibility tree: roles (
button,link,textbox), accessible names ("Search", "Party size") and states (checked,disabled,expanded). - The DOM and text: for content and context.
- Screenshots: a vision model looks at the rendered page, which is slower and more expensive and is usually the fallback.
Every vendor balances these differently, and none of them publish exact details. But the accessibility tree is the compact, structured option, which is why agent frameworks lean on it. If your tree is clean, agents work faster and make fewer mistakes. If it's a pile of unnamed generic nodes, they fall back to guessing from pixels.
See your page the way an agent does
Playwright can print a page's accessibility tree as a YAML-like "ARIA snapshot". It's the fastest way I know to understand what an agent is working with.
npm i -D playwright
npx playwright install chromium
// agent-view.mjs
import { chromium } from "playwright";
const url = process.argv[2] ?? "https://example.com";
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle" });
// The accessibility tree for the main content (fall back to body).
const root = (await page.locator("main").count()) ? page.locator("main") : page.locator("body");
console.log(await root.ariaSnapshot());
await browser.close();
node agent-view.mjs https://your-site.com/booking
Here's a (real-world-style) before and after from a booking widget:
# BEFORE
- generic:
- generic: Book a table
- generic: "2"
- generic: Tonight
- generic: Continue
# AFTER
- heading "Book a table" [level=2]
- combobox "Party size": "2"
- textbox "Date": 2026-10-14
- button "Find available times"
Same visual design. The first version gives an agent four meaningless boxes. The second tells it exactly what to do.
The 8 fixes that matter most
1. Use real buttons and links
<!-- ❌ invisible as an action -->
<div class="btn" onclick="checkout()">Checkout</div>
<!-- ✅ -->
<button type="button" onclick="checkout()">Checkout</button>
Rule of thumb: navigates to a URL → <a href>. Does something on this page → <button>.
2. Label every input
<!-- ❌ placeholder isn't a reliable label -->
<input type="email" placeholder="Email">
<!-- ✅ -->
<label for="email">Work email</label>
<input id="email" type="email" name="email" autocomplete="email" required>
autocomplete tokens are a bonus: they tell any agent precisely what a field expects.
3. Give icon buttons names
<button aria-label="Add to wishlist"><svg aria-hidden="true">…</svg></button>
An unnamed icon button shows up as button with no name. An agent can't pick it with any confidence.
4. Use native form controls (or full ARIA patterns)
Custom dropdowns built from <div>s are the most common thing agents fail on. Use <select> where you can. If you must build a custom combobox, follow the ARIA Authoring Practices pattern completely, states included.
5. Landmarks and headings
One <main>, a <nav>, sensible <h1>–<h3> order. Agents use these to jump straight to the content, just like screen-reader users do.
6. Put errors where they can be read
<input id="zip" aria-describedby="zip-error" aria-invalid="true">
<p id="zip-error">Enter a 5-digit ZIP code.</p>
A red border alone means nothing to an agent. A linked error message tells it how to fix the input and try again.
7. Don't hide key actions behind hover
Hover-only menus and "appears on mouseover" buttons are hard for agents and touch users alike. Make primary actions visible and focusable.
8. Avoid layout shifts after load
Agents act on a snapshot. If your cookie banner or promo bar pushes the button down 80px after the snapshot, the click lands on the wrong element. Reserving space for late content (which you should do for CLS anyway) fixes this too.
Put it in CI
Make the agent view a test, so nobody quietly turns your checkout button back into a div:
// tests/agent-ready.spec.ts
import { test, expect } from "@playwright/test";
test("booking flow is operable from the accessibility tree", async ({ page }) => {
await page.goto("/booking");
// These locators only pass if role + accessible name are correct.
await page.getByRole("combobox", { name: "Party size" }).selectOption("4");
await page.getByRole("textbox", { name: "Date" }).fill("2026-10-20");
await page.getByRole("button", { name: "Find available times" }).click();
await expect(page.getByRole("heading", { name: /available times/i })).toBeVisible();
});
If a test written with getByRole can complete your key flow, an agent has a good chance too. Add @axe-core/playwright for broader accessibility checks.
How this connects to search
Agents don't replace search. They usually start with it: they search, open the top results, then act on the pages. So the pipeline looks like this:
- Get found: classic SEO and AI visibility (the rest of this series).
- Get understood: content in the HTML, clear headings and structured data.
- Get used: an accessibility tree an agent can operate (this post) and optionally WebMCP tools (Part 2).
Most teams only think about step 1. Step 3 is where you lose the conversion after the agent already picked you.
And there's a nice side effect: every fix in this post makes your site better for people using assistive technology. That alone was always reason enough.
Try it: run agent-view.mjs on your highest-value page (signup, checkout, booking, contact) and paste the snapshot into the comments. I'm curious how many generic nodes people find.
Next in the series: Simulate query fan-out to find your content gaps (Python).

Top comments (0)