How does NVDA process the Tab key in browse mode?


Adlevision
 

I’m in the process of designing a course on web accessibility from a screen reader perspective. After using almost every screen reader that has ever existed since the late 80s, I thought I had a good handle on how they worked until now.

I’m flummoxed as to how NVDA (and JAWS and Narrator) is able to synchronize the Tab key in browse mode, such that if the virtual focus is within a normally non-focusable object like a paragraph, system focus moves to the nearest tab-focusable object relative to the virtual caret, rather than relative to the last synchronized browser focus. I can’t tell if and in what manner NVDA is intercepting the Tab key.

For example, assume the following code:
element 1: <button>Hit me</button>
Element 2: <p>I’m only focusable in browse mode</p>
Element 3: <a href=“http://knowbility.org”>These are the folks I’m designing the course for</a>
Element 4: <p>I’m some more text</p>

NVDA behavior: arrowing down from Element 1 to element 4 then pressing Shift-Tab moves focus to element 3, as God intended.

VoiceOver behavior in Safari (an often reliable case for something broken): arrowing from Element 1 to Element 4, then pressing Shift+Tab, moves focus to Element 1, because Element 3 was the last system focus.

I don’t know of any way for the screen reader to send an action to the accessibility API that sets a “here I am” pointer to an object that can’t receive keyboard focus in the absence of a screen reader.

I ran into this puzzle when I realized that web pages can script the Tab key. In the <dialog> element or role=“dialog”, JavaScript can capture the Tab key and make it wrap within the dialog. To me, this implies that screen readers (any of them) are not intercepting the Tab key. Yet, they have to also set keyboard focus relative to the browse mode position when the Tab key is pressed, which requires some sort of processing.

I’m sure the answer is something simple, and I’m overthinking it. But Every possibility I can overthink of wouldn’t work in either the above code example or in a tab ring scripted for a modal.

Thanks in advance,
Neill