The accessibility tree is a simplified copy of the DOM that the browser computes for assistive technology: each node has a role (button, link, heading), an accessible name and states, and nodes with no meaning to a user are left out.
Screen readers read it instead of the HTML. Test tools and AI agents read it too, because it describes a page the way a person would. This entry is part of the screenshot glossary.
What it means in plain terms
The DOM describes how a page is built. The accessibility tree describes what a person can find and do on it. A <div> used only for layout disappears. A <button> with an × icon and aria-label="Close dialog" becomes button "Close dialog". Text hidden with display: none is gone, because no one can perceive it.
The same markup, two views
We loaded this small page in Chrome and printed the tree with the Puppeteer script below:
<nav><a href="/pricing">Pricing</a></nav> <main> <h1>Checkout</h1> <div class="btn" onclick="pay()">Pay now</div> <button aria-label="Close dialog">×</button> <img src="..." alt=""> <label>Email <input type="email" value="[email protected]"></label> <span style="display:none">Hidden promo</span> </main>
| HTML element | In the accessibility tree | Why |
|---|---|---|
<a href="/pricing"> |
link "Pricing" |
Native role; name from the text |
<h1> |
heading "Checkout" |
Native role |
<div onclick>Pay now</div> |
StaticText "Pay now", not a button |
A click handler does not create a role |
<button aria-label="Close dialog">×</button> |
button "Close dialog" |
aria-label replaces the visible × as the name |
<img alt=""> |
absent | Empty alt marks it as decorative |
<input> inside <label> |
textbox "Email " |
Name computed from the label |
<span style="display:none"> |
absent | Hidden from everyone |
The "Pay now" row is the useful finding. On a screenshot it looks like a button. In the tree it is plain text, so a screen reader user cannot find it as a control, and an agent that clicks by role cannot either. The two views disagree, and that disagreement is a real accessibility bug.
How it works technically
The browser derives the tree from the DOM, the computed CSS and ARIA attributes. For each element it computes:
- Role: from the tag (
<button>,<nav>) or an explicitroleattribute. - Accessible name: from
aria-labelledby,aria-label, the label, the content oralt, in a defined order of precedence. - States and properties: checked, expanded, disabled, the heading level, the current value.
The operating system's accessibility API exposes the result to screen readers. Automation tools reach the same data through the Chrome DevTools Protocol. Content inside a shadow DOM is included, which is one reason the tree is easier to work with than querying the DOM.
How to read it
Puppeteer
import puppeteer from 'puppeteer'; const browser = await puppeteer.launch(); try { const page = await browser.newPage(); await page.goto(process.argv[2] ?? 'https://example.com'); // interestingOnly (the default) drops generic wrappers and keeps what assistive tech reads. const tree = await page.accessibility.snapshot({ interestingOnly: true }); const print = (node, depth = 0) => { console.log(`${' '.repeat(depth)}${node.role} "${node.name ?? ''}"`); for (const child of node.children ?? []) print(child, depth + 1); }; print(tree); } finally { await browser.close(); }
Playwright
Playwright's current API is ariaSnapshot(), which returns YAML and is also what its toMatchAriaSnapshot assertion compares:
import { chromium } from 'playwright'; const browser = await chromium.launch(); try { const page = await browser.newPage(); await page.goto(process.argv[2] ?? 'https://example.com'); // ariaSnapshot returns a YAML view of the accessibility tree under the locator. console.log(await page.locator('body').ariaSnapshot()); } finally { await browser.close(); }
For the test page it printed, among other lines:
- main: - heading "Checkout" [level=1] - text: Pay now - button "Close dialog": × - textbox "Email": [email protected]
Tested on 2026-10-15 with Node 22.18.0, [email protected] (Chrome 148, run with headless: 'shell') and [email protected] (Chromium 156).
RenderScreenshot
RenderScreenshot returns rendered images and PDFs. It does not return an accessibility tree. The two are complementary: the tree tells you what is on the page, and a capture tells you how it looks. A common agent pattern reads the tree with Playwright, then requests a capture of only the region in question to check layout, using capture.selector:
{ "url": "https://shop.example.com/checkout", "capture": { "selector": "main" }, "output": { "format": "png" } }
How the two compare in size and usefulness for language models is measured in screenshot vs DOM vs accessibility tree. This request was not run for this page.
Common mistakes
- Treating
display: noneandvisibility: hiddencontent as readable. It is not in the tree. - Clickable
<div>and<span>elements without a role. They look fine on screen and vanish as controls. - Icon buttons without names. They appear as
button "". - Snapshots of a page that is not ready. The tree reflects the current DOM. Use the same wait strategy you would use for a screenshot.
- Assuming the tree covers visual problems. Contrast, overlap and clipped text are invisible to it.
Related terms
Building an agent that needs to see pages too? Read about screenshots for AI agents or get a free API key.