Note: The following steps show my understanding of how Chrome builds its accessibility tree, based on Chromium’s open-source code. Source references are listed at the end.
We’re often told that the browser builds its accessibility tree ‘from the DOM,’ but what does that actually involve?
Let’s look at how Chrome builds its accessibility tree, from start to finish.
1. Identify which DOM nodes are “interesting” for accessibility
Chrome walks the DOM and filters out anything that has no semantic or interactive value — such as hidden elements, inert content, non-semantic containers, and visual-only wrappers.
2. Compute the AXRole for each element
Blink works out the accessibility role using native HTML first, then ARIA, with a fallback if the ARIA role cannot be used.
3. Compute the Accessible Name + Description
Chrome runs the full Accessible Name and Description Computation algorithm using aria-labelledby, aria-label, native naming rules, and text content.
4. Build the AXNode for this DOM node
For every exposed element, Blink creates an AXNode containing its role, name, description, states, value, and other accessibility metadata.
5. Build the AX Tree Hierarchy (Parent/Child Relationships)
Chrome builds parent and child relationships using the DOM structure, ARIA such as aria-owns, and extra nodes created for some controls such as lists, tables and form elements.
6. Compute Relationships
Blink connects related accessibility information, including aria-labelledby, aria-describedby, aria-controls, aria-activedescendant, and table relationships.
7. Compute States and Properties
Chrome works out states such as focus, checked, expanded, disabled, busy, required and selected, and adds any relevant ARIA states and properties.
8. Compute Bounding Boxes + Layout-dependent attributes
Chrome works out the onscreen position and size of each accessibility node. This helps assistive technology find and move between elements on the page.
9. Finalise the AXTree snapshot
Blink builds a stable version of the accessibility tree, stores calculated values, and tracks which nodes have changed.
Note: This “what changed?” information is crucial. Blink uses it to decide whether an accessibility event needs to be sent. It is the differences between the old and new snapshots that trigger accessibility events.
10. Generate AXEvents based on tree changes
Chrome compares the old and new accessibility data, then sends events when something important has changed, such as a name, value, focus or live region.
11. Deliver the AXTree + AXEvents to the OS Accessibility API
Blink maps its internal accessibility nodes to the platform accessibility API, such as AX API, UIA or AT-SPI, and updates the accessibility tree.
12. OS API delivers events → Assistive Technology receives them
The operating system hands these events to the screen reader, which then interprets them and decides what, and when to speak.
And all of this happens in the blink of an eye (pun intended).
Modern browsers run through all these steps continuously, incrementally, and with a lot of optimisation. This often within microseconds!
Sources
Blink Accessibility Core: ax_object.cc
What it covers:
- Determines which DOM nodes are exposed
- Computes roles, names, descriptions, states, relationships, value computation, etc.
- Contains the logic that turns DOM nodes into Blink’s internal AXObjects.
AX Tree Serialization: ax_tree_serializer.h
What it covers:
- Maintains a “client-known tree”
- Computes differences between snapshots of the AXTree
- Generates accessibility events (AXEvents)
AXNode Structure: ax_node.h
What it covers:
- Defines the actual AXNode objects
- Holds computed properties for name, role, states, value
- Defines parent/child relationships
- Is the structure that gets serialized