Screen Reader Modes: Essential Knowledge for Front-End Developers
Screen Reader Modes: Essential Knowledge for Front-End Developers

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: 

  1. On the Recipe Dashboard page, the Edit (pencil) icon is keyboard-operable when NVDA or JAWS is enabled. However, it is not keyboard-operable when a NVDA is off or when using VoiceOver on Macos.
  2. Action cannot be performed with a screen reader turned ON (Failure of  WCAG SC 2.1.1)   on this page.
  3. Focus order issue on this page.


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."

  • Examples:

<div> , <button>, role="button"; <a href> , role="link"; <input type="checkbox">, role="checkbox", <input type="radio">, role="radio".        

  • Behaviour: When you Tab onto these elements,  NVDA/JAWS (in its default setting) does not automatically switch to Focus Mode for these elements. It allows you to continue reading the page linearly in the browse mode.  
  • When NVDA is running in its Browse Mode, it sits between your keyboard and the browser. 
  • When Enter or Space is pressed, NVDA intercepts the input and triggers a synthetic click() event, bypassing the keydown listener. The Recipe Dashboard Page scenario described above works with NVDA and JAWS for this reason..


Input Controls (Switch to Focus Mode): These are elements that require raw keyboard input (typing letters or using arrow keys for internal navigation).

  • Examples: 

<input type="text">, <textarea>, <select>, role="grid", role="menu", role="tablist        

  • Behavior: When you Tab onto these elements, NVDA detects that you need to type or navigate internally. It plays a distinctive sound (often a "thunk" or "click") and switches to Focus Mode. 
  •  Now, your keystrokes go directly to the browser.
  • Enter/Space are sent as keydown events. Your keydown listener would work here.


Browse Mode vs. Focus Mode

Understanding why this happens requires understanding NVDA modes:

Table with three columns: Mode, What it does, and Result in your code.

Browse Mode: NVDA intercepts keystrokes to enable reading and navigation (e.g., “H” for heading).
Result: Pressing Enter or Space triggers a click event instead of a keydown event; the keydown event is blocked. This behavior explains the three accessibility issues referenced in the problem statement.
Focus Mode: Keystrokes are passed directly to the browser (used for forms and interactive applications).
Result: Enter or Space is received as a keydown event, so a keydown listener functions as expected.



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();
    }
});
        


To view or add a comment, sign in

Others also viewed

Explore content categories