Accessibility · Assistive Technology
Virtual Cursors and Browse Mode
How a screen reader maintains a separate reading cursor over a virtual buffer of the page, distinct from real keyboard focus, and why the two desynchronize.
A sighted reader does not type into a web page the way one types into a text field. The page is read from top to bottom, and interaction happens at one spot at a time. A screen reader models this with two separate cursors that only sometimes coincide: the real keyboard focus, which is the operating system’s notion of where keystrokes go, and a virtual cursor that moves through the page content freely. The split between the two, and the machinery that keeps them roughly in sync, is the core of how a screen reader reads documents rather than just operating controls.
Why one cursor is not enough
Keyboard focus is the wrong model for reading. Focus moves between interactive elements only: links, buttons, form fields. The text between them is not focusable, so a reader using only focus would announce the controls and skip the prose. To read the prose, the screen reader needs a position that can stop anywhere, including in the middle of a paragraph that has no focusable element at all.
The virtual cursor is that position. It is not a focus in the operating system’s sense; it is a location inside a private copy of the document that the reader maintains, and it can move character by character, word by word, or element by element through the entire content. The reader exposes keystrokes for these movements, so the user reads by advancing the virtual cursor, and the reader announces whatever the cursor crosses. When the user reaches a control and activates it, the virtual cursor hands off to real focus, and for the duration of the interaction the two coincide.
This is the browse mode that most desktop screen readers use for documents by default. The name captures the intent: in browse mode the primary action is reading, and interaction is entered deliberately. In focus mode, or forms mode, the keystrokes pass through to the application as if typed directly, so a form field receives ordinary editing input.
The virtual buffer
The virtual cursor cannot walk the live DOM directly, because the DOM is a tree of objects, not a flat reading order, and because reading requires a stable order that matches what the user sees. The reader therefore builds a virtual buffer: a linearized, simplified copy of the document that flattens the DOM into the order the reading cursor will traverse.
The linearization follows the document order, which is roughly the source order of the elements. The buffer stores, for each readable unit, the text, the role, and enough structure to reconstruct relationships like headings and lists. It is a snapshot: it is built when the page loads or changes substantially, and it reflects the document as it was at that moment. Reading proceeds against the snapshot, which is why a page that updates dynamically can leave the reader reading content that no longer exists on screen.
Because the buffer is a copy, it also costs memory. A large page produces a large buffer, and the reader trades that memory for the ability to navigate instantly without re-querying the application for every element. The buffer is rebuilt on demand when the page changes, and the reader must decide how much change triggers a rebuild versus patching the existing buffer in place.
Handing off to focus mode
The transition between the two cursors is the most failure-prone part of the design. When the user activates a link or control from the virtual cursor, the reader sets real focus to the corresponding element and switches to focus mode, so subsequent keystrokes go to the control. When the user finishes and leaves the control, the reader switches back to browse mode and restores the virtual cursor to the position where the interaction began, or near it.
The reader decides when to enter focus mode automatically, using the role of the focused element. A text field, a combobox, or a contenteditable region requires keystrokes to pass through, so the reader switches to focus mode on entering one. A link or a button does not require pass-through in the same way, so activation does not necessarily switch modes. The automatic switching is a heuristic, and it is configurable, because no single policy suits every user or every site.
The cost of the handoff is the desynchronization window. Between the moment the reader decides to switch modes and the moment the application reflects the switch, there is a state where the virtual cursor and the real focus point at different things. A keystroke in that window can go to the wrong target. The reader minimizes the window, but it cannot eliminate it, because the two cursors live in different processes and the switch is a cross-process operation.
Where the cursors drift
Desynchronization is the central failure mode, and it has several distinct causes.
Dynamic content is the first. The virtual buffer is a snapshot, so content inserted or removed after the buffer was built is not reflected. The user’s virtual cursor sits at a position that corresponds to content that has moved or vanished. When the reader tries to map the buffer position back to a live DOM node to act on it, the mapping may fail or land on the wrong node.
The second cause is the map itself. The buffer stores a correspondence between its units and the live nodes they came from, and that correspondence is maintained by identity. If the page replaces a node with a structurally identical one, the identity changes and the mapping is stale, even though the visible text looks the same. Single-page applications that re-render subtrees on every state change are the worst case, because they invalidate the mapping continuously.
The third cause is focus moved by script. A page can call focus on an element at any time, moving the real focus without the virtual cursor’s knowledge. The reader must detect this and reconcile, which it does by treating external focus moves as commands to move the virtual cursor too, but the detection is asynchronous and the reconciliation is another window of drift.
The cost of the two-cursor model
The alternative to two cursors is a single focus that the reader extends to non-focusable content by making everything focusable or by synthesizing focus events. That alternative breaks the page’s own keyboard model and its tab order, and it is the reason the virtual buffer exists despite its complexity. The two-cursor design accepts desynchronization as the price of not distorting the document’s real semantics.
The virtual cursor is also the layer on which most reading commands operate. Quick navigation, the keystrokes that jump to the next heading or the next list or the next link, runs against the buffer, not against the DOM, because the buffer has precomputed the structure the commands need. This is why quick navigation is fast even on large pages and why it goes stale on pages that change: it is a property of the snapshot.
The relationship to the rest of the stack is direct. The buffer stores roles and text that the accessible name computation produced, and the announcements the reader makes while reading come from the announcement pipeline applied to buffer entries. The virtual cursor is what makes a screen reader a reading tool rather than merely a focus announcer, and its drift is the trade-off the design accepts in exchange.
References
- WAI-ARIA 1.2 — W3C
- HTML Living Standard — WHATWG
- Understanding SC 2.1.1: Keyboard — W3C