How Chromium Builds the Accessibility Tree

This article provides an overview of the architectural process Chromium uses to transform a web page's Document Object Model (DOM) into an internal Accessibility Tree, bridging the gap between raw web code and assistive technologies like NVDA and JAWS. It covers how the Blink rendering engine extracts semantic data from the DOM and CSS, how this data crosses security boundaries via inter-process communication, and how the main browser process exposes the final tree to operating system accessibility APIs.

1. Extracting Semantics in the Renderer Process

The construction of the Accessibility Tree begins inside Chromium’s sandboxed Renderer Process, managed by the Blink rendering engine. Chromium does not build the accessibility tree from the DOM alone; it combines the DOM tree with the Layout Tree (which includes computed CSS styles).

CSS directly impacts accessibility. For example, elements styled with display: none or visibility: hidden are excluded from the accessibility tree, while CSS pseudo-elements (::before and ::after) are included.

Blink traverses the DOM and Layout trees simultaneously to create a parallel structure of AXObject instances. During this phase, Blink performs two critical actions:

  • Filtering: Not every DOM node becomes an AXObject. Purely visual or structural containers, such as a generic <div> or <span> used only for styling, are ignored to keep the tree clean and navigable.
  • Computation: For every relevant node, Blink calculates its core accessibility properties: Role (what the element is, e.g., button, heading), Name (its accessible label), State (e.g., checked, disabled, expanded), and Value. This calculation follows strict precedence rules, combining native HTML semantics with WAI-ARIA attributes (such as aria-label or role).

2. Serialization and Inter-Process Communication (IPC)

Because Chromium uses a multi-process architecture, the Renderer Process is heavily sandboxed for security. Screen readers like NVDA and JAWS run at the operating system level and cannot directly query the Renderer Process. Furthermore, screen readers require fast, synchronous responses to user commands; querying the Renderer directly could cause the screen reader to freeze if the web page's main thread is busy running heavy JavaScript.

To solve this, Chromium caches a copy of the accessibility tree in the main Browser Process.

Whenever the DOM or layout changes in a way that affects accessibility, Blink batches these modifications and serializes the affected AXObject nodes into a data structure called AXTreeUpdate. Chromium then sends this serialized data across process boundaries to the Browser Process using Mojo, Chromium's inter-process communication (IPC) system.

3. Reconstructing the Tree in the Browser Process

Once the AXTreeUpdate message arrives in the unsandboxed Browser Process, it is deserialized to construct or update a platform-independent representation of the tree known as ui::AXTree, which is composed of ui::AXNode instances.

A component called the BrowserAccessibilityManager oversees this tree. For every ui::AXNode, the manager creates a platform-specific wrapper object called BrowserAccessibility. This separation allows Chromium to maintain one core logic system for tree management while adapting the output to fit the specific requirements of Windows, macOS, Linux, Android, or ChromeOS.

4. Exposing Content to NVDA and JAWS

The final step is translating the BrowserAccessibility objects into formats that Windows-based screen readers like NVDA and JAWS can understand.

On Windows, assistive technologies rely on specific Component Object Model (COM) APIs to interact with applications. Chromium supports the following APIs for Windows screen readers:

  • Microsoft Active Accessibility (MSAA): A legacy API still used for basic compatibility.
  • IAccessible2 (IA2): An API that extends MSAA, heavily relied upon by both NVDA and JAWS for rich web content.
  • UI Automation (UIA): Microsoft's modern accessibility framework.

The BrowserAccessibility wrappers in Chromium implement the interfaces for these exact APIs (such as IAccessible2 on Windows). When a user navigates a page using NVDA or JAWS, the screen reader makes API calls directly to the Chromium Browser Process. The Browser Process immediately answers these queries using its cached ui::AXTree without waiting on the Renderer.

Similarly, when a web page dynamically updates—such as a modal opening or an aria-live region changing—Blink detects the change, sends an AXTreeUpdate and an associated event to the Browser Process, and the Browser Process fires a native Windows OS event. NVDA or JAWS intercepts this event, queries the updated node in the ui::AXTree, and announces the change to the user.