Single-Character Shortcuts and SC 2.1.4
Permalink to "Single-Character Shortcuts and SC 2.1.4"A single-character shortcut is a keyboard command triggered by one printable key with no modifier: J for next row, E to archive, S to star, / to jump to search. They make data-heavy applications fast for keyboard users. They also misfire constantly for two groups: speech-input users, whose dictated words are delivered as keystrokes (saying “next” can type n, e, x, t and trigger four commands), and screen reader users, whose own single-key navigation commands collide with the application’s.
SC 2.1.4 Character Key Shortcuts (Level A) requires a way out. This page covers the three compliant options and how to implement them without taking shortcuts away from the users who rely on them. It belongs to keyboard shortcuts & command palettes.
Spec reference
Permalink to "Spec reference"SC 2.1.4 applies when a keyboard shortcut is implemented using only letter (including upper- and lower-case), punctuation, number, or symbol characters. At least one of these must be true:
- Turn off — a mechanism is available to turn the shortcut off.
- Remap — a mechanism is available to remap the shortcut to include one or more non-printable keys (Ctrl, Alt, etc.).
- Active only on focus — the shortcut is only active when the relevant user interface component has focus.
Shortcuts that already include a modifier (Ctrl+K, Alt+Shift+N) are out of scope. So are keys inside a component that is focused and uses them as part of its own keyboard model, such as type-ahead in a listbox — that is the “active only on focus” case.
Related: SC 2.1.1 Keyboard (the functions must still be available by keyboard if the shortcuts are off) and SC 3.2.1 On Focus.
When to use single-key shortcuts — and when not to
Permalink to "When to use single-key shortcuts — and when not to"Use them for high-frequency actions in a focused work surface: triaging a queue, moving through rows, starring and archiving. Scope them to that surface where you can.
Avoid them for destructive actions unless they are undoable. A misfired D that deletes a record — because a speech user said “done” — is the worst outcome this criterion exists to prevent. Put deletion behind a modifier or a confirmation, or make it undoable, as in confirming and undoing destructive row actions.
The misapplication to name is a global keydown listener on document that fires on any letter, everywhere, including while focus is in a search field. Users type “report” into search and the application stars, archives and opens things.
Annotated code example
Permalink to "Annotated code example"// Shortcut registry with a user setting and focus scoping
const settings = loadSettings(); // { singleKeyShortcuts: 'on' | 'off' | 'modifier' }
const SHORTCUTS = [
{ key: 'j', scope: '#queue-grid', run: nextRow, label: 'Next row' },
{ key: 'k', scope: '#queue-grid', run: prevRow, label: 'Previous row' },
{ key: 'e', scope: null, run: archive, label: 'Archive' }, // global
{ key: '/', scope: null, run: focusSearch, label: 'Search' }, // global
];
document.addEventListener('keydown', (e) => {
if (e.defaultPrevented || e.isComposing) return;
const t = e.target;
// Never fire while the user is typing text
if (t.closest('input, textarea, select, [contenteditable="true"], [role="combobox"]')) return;
const hasMod = e.ctrlKey || e.metaKey || e.altKey;
for (const s of SHORTCUTS) {
if (e.key.toLowerCase() !== s.key) continue;
// SC 2.1.4 option 3: scoped shortcuts only fire inside their component
if (s.scope && !t.closest(s.scope)) continue;
// SC 2.1.4 options 1 and 2: global shortcuts obey the user setting
if (!s.scope) {
if (settings.singleKeyShortcuts === 'off') continue;
if (settings.singleKeyShortcuts === 'modifier' && !(e.altKey && e.shiftKey)) continue;
if (settings.singleKeyShortcuts === 'on' && hasMod) continue;
}
e.preventDefault();
s.run();
return;
}
});
<!-- The setting, reachable from the shortcut help dialog and account settings -->
<fieldset>
<legend>Single-key shortcuts</legend>
<label><input type="radio" name="sks" value="on" checked> On</label>
<label><input type="radio" name="sks" value="modifier"> Require Alt+Shift</label>
<label><input type="radio" name="sks" value="off"> Off</label>
</fieldset>
Scoped shortcuts (J and K on the queue grid) are compliant regardless of the setting, because they only work while the grid has focus. Global ones (E, /) obey the setting. That split lets you keep the fastest keys for focused work without making every page a minefield.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Situation | Expected behaviour | Failure indicator |
|---|---|---|
| Typing “report” in search | Letters typed; no shortcuts fire | Items starred, archived or opened |
NVDA browse mode, pressing E |
NVDA moves to next edit field; app ignores it | App archives and NVDA moves |
Grid focused (focus mode), J |
Next row | Nothing, or page scrolls |
| Dragon: “click archive” | Clicks the Archive button | Typed letters trigger shortcuts |
Setting = off, E on page |
Nothing happens | Archive fires |
Screen readers in browse mode normally consume letter keys before the page sees them, so collisions usually appear when the reader is in focus mode on a non-text element — or when users turn on pass-through. Scoping shortcuts to a component with a composite role (grid, listbox) puts the reader in focus mode exactly where the shortcuts live.
Integration context
Permalink to "Integration context"Shortcuts should be exposed to assistive technology on the controls they activate with aria-keyshortcuts, as in aria-keyshortcuts for grid commands, and listed — with the on/off setting — in the help dialog from keyboard shortcut help dialogs.
A command palette (Ctrl+K) is an excellent companion to single-key shortcuts: it uses a modifier, so it is outside 2.1.4, and it gives every command a discoverable, searchable home; see an accessible command palette for data apps.
Gotchas
Permalink to "Gotchas"e.key versus layout. On non-QWERTY layouts, e.key gives the character typed, which is usually what you want for letter shortcuts. Using e.code (KeyE) binds to a physical position instead — better for J/K navigation, worse for mnemonic letters.
IME composition. Ignore keydown events with isComposing set, or users typing Chinese or Japanese trigger shortcuts mid-composition.
Remembering the setting. Store it per user, not per device, where you have accounts; speech users often switch machines.
Design system notes
Permalink to "Design system notes"Provide a shortcut registry as a platform service rather than letting components add document listeners. The registry enforces the text-field guard, the user setting and scoping in one place, and it is the single source for the help dialog and for aria-keyshortcuts values on buttons. Components declare shortcuts; they never listen for them directly.
Testing checklist
Permalink to "Testing checklist"FAQ
Permalink to "FAQ"Does SC 2.1.4 ban single-key shortcuts?
No. It requires that users can turn them off, remap them to include a modifier key, or that each shortcut only works while the relevant component has focus. Shortcuts can stay on by default as long as one of those is true.
Why are single-key shortcuts a problem for speech input users?
Speech recognition software types dictated words as keystrokes. On a page with global single-letter shortcuts, saying a word can trigger a command for each letter.
Do shortcuts with Ctrl or Alt need to meet SC 2.1.4?
No. The criterion only covers shortcuts made of printable characters alone. Shortcuts with a modifier key are out of scope, though they should still avoid clashing with browser and screen reader keys.
Should shortcuts work while a text field has focus?
Not single-character ones. Ignore them whenever focus is in an editable element, so typing never triggers commands.
Related
Permalink to "Related"- aria-keyshortcuts for grid commands — exposing shortcuts to AT
- Keyboard shortcut help dialogs — documenting them
- Shortcuts for result page navigation — paging shortcuts