Screen readers speak from a virtual buffer, not from pixels
Aliases: off-screen model · virtual cursor · accessibility snapshot
What it is
A screen reader does not narrate pixels. It copies what the accessibility API currently exposes into a private document, then walks a virtual cursor through that copy and speaks. That copy is the virtual buffer (also called an off-screen model). The eye sees boxes from the layout engine; the ear hears a node list from the buffer. They were never the same data.
“It reads the screen” is the OCR fallback, not the default path. On the default path, anything that never entered the accessibility tree does not exist for the reader, however sharp it looks on the canvas.
Why it happens
The browser or OS exposes an accessibility tree; the reader serializes that tree into the buffer. Painting goes through the compositor — layers, transforms, clipping, alpha. Serialization goes through a different pipe — roles, names, text nodes, containment. There is no bridge that says “speak this pixel’s colour.” So display: none and aria-hidden drop a node from the buffer, while “moved off-screen” or “opacity zero” often leave it there: hidden to sight, still in the copy.
The second layer is snapshot, not livestream. The cursor walks a document that has already been copied, not the frame buffer of the current frame. That is why document-style browsing (by element, by word) is possible, and why the reader always sees “the tree as of the last copy,” not “the flash happening on screen right now.”
Studying it
Align three artefacts, do not stop at one listen-through: the tree in an accessibility inspector, the reader’s own buffer view (NVDA’s element list, JAWS’s virtual viewer), and the speech produced by arrow keys with the display covered. On Windows web content use NVDA+Firefox or JAWS+Chrome; for native apps use the platform inspector and do not assume an HTML-style buffer exists.
Independent variables: whether a node enters the tree (display:none / visually hidden / aria-hidden / paint-only canvas), platform (Windows web readers / VoiceOver / TalkBack).
Dependent variables: whether speech includes the node, whether the buffer list includes it, whether inspector and speech are isomorphic.
Automation can see “an attribute is present.” It cannot see which edition of the tree the reader actually copied.
Where it stops holding
The classic virtual buffer is a Windows web-reader architecture (JAWS, NVDA). VoiceOver and TalkBack consume the accessibility API more directly; the buffering shape differs, and Insert-key refresh lore does not transfer. Native desktop or mobile widgets often have a control tree and no full-page HTML buffer. When the user turns on OCR or image recognition, the reader does read pixels — that is a repair after the buffer came up empty, not proof that the default path is optical. Games, maps, and WebGL that hand frames only to the GPU leave the buffer blank.
Applying it
- Anything that must be heard needs a node in the accessibility tree; painting it on a canvas or in a CSS background is not enough.
- Hide visually with techniques that keep the node in the tree; do not use
display: noneoraria-hiddenon content that still has to be spoken. - The reverse for decoration: take it off the tree, do not merely make it faint.
- How to check: match speech against the inspector; then open NVDA or JAWS’s buffer/element list and walk the page with the screen covered. Inspector-yes/speech-no, or pixels-yes/both-lists-no, means the copy and the paint have already diverged.
Related
- Same group: J5.09.2 Browse mode and focus mode bind the same keys to different jobs · J5.09.3 Buffer order follows document structure, not CSS visual position · J5.09.4 After dynamic updates the buffer must be rebuilt or speech is stale
- Nearby: J5.01 Screen readers · J5.02 Accessibility tree and roles
- Search terms:
virtual buffer·off-screen model·accessibility tree