Single-letter shortcuts can fire by accident for speech input and keyboard users. Offer a way to turn them off, remap them, or limit them to focus.
Many document viewers and case systems add keyboard shortcuts such as d to download or / to jump to search. For a person who dictates, a spoken sentence can be turned into a stream of key presses, and a stray letter can delete, archive or navigate away. People who type with one finger or a mouth stick also hit keys by accident.
Success Criterion 2.1.4 applies when a shortcut is made of character keys only: letters, numbers, punctuation or symbols, including sequences like pressing g then a. Shift+/ to produce a question mark still counts. In that case at least one of three things must be true.
Shortcuts that already need a modifier such as Ctrl or Alt are not affected.
d for download and p for print./ or n to move around results.The shortcuts are usually added as a convenience for staff and then shipped to the public unchanged. A visible switch in settings is often all the fix needs.
A preference stored for the user, honored before any shortcut runs. Put it in a place that can be reached with the keyboard, for example a "Keyboard shortcuts" setting on the page.
document.addEventListener("keydown", e => {
if (e.key === "d") downloadDocument();
if (e.key === "p") printDocument();
});const prefs = JSON.parse(
localStorage.getItem("docketPrefs") || '{"shortcuts":true}'
);
shortcutToggle.checked = prefs.shortcuts;
shortcutToggle.addEventListener("change", () => {
prefs.shortcuts = shortcutToggle.checked;
localStorage.setItem("docketPrefs", JSON.stringify(prefs));
});
document.addEventListener("keydown", e => {
if (!prefs.shortcuts) return;
if (e.ctrlKey || e.metaKey || e.altKey) return;
if (e.target.closest("input, textarea, select, [contenteditable]")) return;
if (e.key === "d") downloadDocument();
if (e.key === "p") printDocument();
});A hard-coded map becomes a user-editable map. Bindings must be allowed to include a modifier key.
const KEYS = { d: downloadDocument, p: printDocument };
document.addEventListener("keydown", e => KEYS[e.key]?.());// Loaded from the user's settings page, e.g. {"Alt+KeyD": "download"}
const bindings = loadBindings();
const actions = { download: downloadDocument, print: printDocument };
document.addEventListener("keydown", e => {
const combo = (e.ctrlKey ? "Ctrl+" : "") +
(e.altKey ? "Alt+" : "") + e.code;
const name = bindings[combo];
if (name) { e.preventDefault(); actions[name](); }
});A results list that supports "n" for next page and "p" for previous page can keep that behavior if the keys act on the list itself, not on the whole document.
document.addEventListener("keydown", e => {
if (e.key === "n") nextPage();
if (e.key === "p") prevPage();
});const results = document.getElementById("results"); // tabindex="0"
results.addEventListener("keydown", e => {
if (e.key === "n") nextPage(); // only while the list is focused
if (e.key === "p") prevPage();
});Apps are covered too. Tablets and phones with hardware keyboards expose the same risk. On iOS and Android, ask for a modifier key by default. In Compose, onKeyEvent only receives events while that node or one of its children has focus, so attach it where focus actually is.
SwiftUI:
Button("Download") { download() }
.keyboardShortcut("d", modifiers: [])Button("Download") { download() }
.keyboardShortcut("d", modifiers: .command)Jetpack Compose:
Modifier.onKeyEvent { event ->
if (event.type == KeyEventType.KeyDown && event.key == Key.D) {
download(); true
} else false
}Modifier.onKeyEvent { event ->
if (event.type == KeyEventType.KeyDown &&
event.isCtrlPressed && event.key == Key.D) {
download(); true
} else false
}keydown and keypress handlers to find undocumented ones. Access keys that are activated with a modifier are not affected.Automated tools cannot see what a script does with a key press, so this one is manual.
Ship it when every single-key shortcut can be turned off, remapped with a modifier, or fires only while its own component is focused, and the choice is saved.
See WCAG 2.1 AA and the ADA for how this level A criterion sits in the standard.