Screen Reader Modes: Essential Knowledge for Front-End Developers
A common accessibility problem found during accessibility testing is that buttons and other widgets do not function the same way when a screen reader like NVDA or JAWS is turned on. This issue mostly affects how the keyboard works with these elements.
This article explains why this happens. The main reason is how screen readers work in different modes, such as Browse Mode and Focus Mode.
Understanding these screen reader modes and how they handle keyboard input is important for front-end developers. This article also explains how to fix the issue so the UI control works correctly for all users, including screen reader users.
Problem Statement:
The Interactive element works as expected when a screen reader is not in use; However, it fails to function when NVDA or JAWS is turned on. This is a common issue identified by accessibility testers during audits. The following sections will explore the root cause of this behaviour and outline recommended remediation approaches for front-end developers.
Below are three scenarios that demonstrates the problem statement:
Root cause of the Issue:
The primary root cause of this issue is relying solely on a keydown event listener on the trigger element.
Further Deep Dive Analysis
Screen readers like NVDA, JAWS categorise HTML elements into two broad types to decide which mode to use:
Command Controls (Stay in Browse Mode): These are simple elements where the only interaction is "Activate."
<div> , <button>, role="button"; <a href> , role="link"; <input type="checkbox">, role="checkbox", <input type="radio">, role="radio".
Input Controls (Switch to Focus Mode): These are elements that require raw keyboard input (typing letters or using arrow keys for internal navigation).
Recommended by LinkedIn
<input type="text">, <textarea>, <select>, role="grid", role="menu", role="tablist
Browse Mode vs. Focus Mode
Understanding why this happens requires understanding NVDA modes:
However, the behaviour is different for voiceover (MacOS).
This difference exists because macOS (Voiceover) and Windows (NVDA/JAWS) use completely different architectures to interact with web pages.
NVDA (Windows):
Uses a Virtual Buffer. It creates a text-copy of the webpage. When you navigate, you are navigating the copy, not the real page. NVDA must strictly intercept keys to control this copy, blocking them from the browser.
JAWS (Windows):
Just like NVDA, JAWS stays in Virtual PC Cursor mode when navigating command controls elements such as buttons. It only switches to Forms Mode (where keydown would work) for input control elements like <input type="text">
Voiceover (macOS):
Voiceover does not use a Virtual Buffer. It interacts directly with the browser's DOM. Because there is no "Browse Mode" layer blocking the keyboard, standard keys like Enter often pass directly to the browser, triggering your keydown event.
SOLUTION:
To fix this type of issue, ensure that all the core functionality on the Command Control elements is written inside the click event handler function.
In the keydown event handler function, implicitly call the click() function.
testButton.addEventListener("click", () => {
// Implement the required functional logic in this section.
});
testButton.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
testButton.click();
}
});