aria-keyshortcuts for Grid Commands

Permalink to "aria-keyshortcuts for Grid Commands"

aria-keyshortcuts tells assistive technology which keyboard shortcut activates an element. On a grid toolbar’s “Archive” button, aria-keyshortcuts="E" lets a screen reader announce “Archive, button, E” — so users learn the shortcut from the control itself, the way sighted users learn it from a tooltip. It prevents shortcuts from being a sighted-only feature, known only to people who hovered over a button.

It is also one of the most misunderstood attributes in ARIA, because it does nothing to the keyboard: it describes a shortcut that your code must implement. This page covers its syntax, placement, announcements and the sync problem with remappable shortcuts. It belongs to keyboard shortcuts & command palettes.

Spec reference

Permalink to "Spec reference"

ARIA 1.2 defines aria-keyshortcuts as a string of one or more shortcuts separated by spaces. Each shortcut is a set of keys joined by +, with modifiers first. Key names follow the UI Events key values: Control, Alt, Shift, Meta, and printable characters or names such as Delete, ArrowDown, Enter. Examples: Control+Shift+E, Delete, Alt+ArrowUp Alt+K (two alternatives).

Rules that matter:

  • It must be placed on the element the shortcut activates or focuses.
  • It does not create any key handling; authors implement it.
  • Authors should avoid shortcuts that conflict with browser, operating system or assistive technology keys.
  • Use the key value that the user actually types; Shift+E means Shift and E, not uppercase E alone.

It is related to, but different from, accesskey, which does implement a browser-level shortcut with browser-specific modifiers and is generally avoided in applications.

Criteria: SC 4.1.2 Name, Role, Value (additional properties of the control), SC 3.3.2 Labels or Instructions (discoverability), and SC 2.1.4 for any single-character keys it describes.

Anatomy of one aria-keyshortcuts value Steps that build the value Control+Shift+E Delete: modifiers first, then the key, joined with plus signs, with alternatives separated by a space. Anatomy of one aria-keyshortcuts valueModifiers firstControl, Alt, Shift, Meta — in any order, before the keyUI Events key namesThen the keya character like E, or a name like Delete or ArrowDowncase-sensitive namesJoin with +Control+Shift+Eno spaces inside a shortcutSpace between alternatives"Control+Shift+E Delete"either one works
Two alternatives for one command, each written modifiers-first.

When to use it — and when not to

Permalink to "When to use it — and when not to"

Use it on every visible control that has a keyboard shortcut: toolbar buttons, menu items, the search field that / focuses. It is cheap, and it is the only way the shortcut reaches users who never see a tooltip.

Do not put it on the grid container to describe navigation keys. Arrow keys, Home and End inside a grid are part of the grid role’s expected behaviour; listing them in aria-keyshortcuts adds noise and they are not “shortcuts” to a control. Document them in the help dialog.

Do not use it without implementing the shortcut, and do not use it as a substitute for a visible hint. Many screen readers announce it only in certain verbosity modes, so it supplements rather than replaces documentation.

The misapplication to name is setting aria-keyshortcuts="E" on a button while the actual handler listens for Shift+E after a refactor. The attribute now teaches users the wrong key.

Annotated code example

Permalink to "Annotated code example"
<div role="toolbar" aria-label="Queue actions" aria-controls="queue-grid">
  <!-- SC 4.1.2: the shortcut is a property of the control it activates -->
  <button type="button" data-command="archive" aria-keyshortcuts="E"
          title="Archive (E)">Archive</button>
  <button type="button" data-command="assign" aria-keyshortcuts="Alt+A"
          title="Assign (Alt+A)">Assign…</button>
  <!-- two alternatives: Delete, or Control+Backspace -->
  <button type="button" data-command="delete" aria-keyshortcuts="Delete Control+Backspace"
          title="Delete (Delete)">Delete…</button>
</div>
<input type="search" aria-label="Search queue" aria-keyshortcuts="/">
// One registry drives the handler AND the attribute, so they cannot drift apart
const registry = {
  archive: { keys: ['E'], run: archiveSelected },
  assign:  { keys: ['Alt+A'], run: openAssign },
  delete:  { keys: ['Delete', 'Control+Backspace'], run: confirmDelete },
};

function syncAttributes() {
  document.querySelectorAll('[data-command]').forEach((el) => {
    const cmd = registry[el.dataset.command];
    el.setAttribute('aria-keyshortcuts', cmd.keys.join(' '));   // ARIA syntax
    el.title = `${el.textContent.trim().replace('…', '')} (${cmd.keys[0].replace('Control', 'Ctrl')})`;
  });
}

function matches(e, combo) {
  const parts = combo.split('+');
  const key = parts.pop();
  const mods = { Control: e.ctrlKey, Alt: e.altKey, Shift: e.shiftKey, Meta: e.metaKey };
  const wanted = new Set(parts);
  return e.key.toLowerCase() === key.toLowerCase()
    && Object.entries(mods).every(([m, on]) => on === wanted.has(m));
}

document.addEventListener('keydown', (e) => {
  for (const cmd of Object.values(registry)) {
    if (cmd.keys.some((k) => matches(e, k))) { e.preventDefault(); cmd.run(); return; }
  }
});

// After the user remaps a shortcut in settings:
function remap(command, keys) { registry[command].keys = keys; syncAttributes(); }

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Reader Announcement for the Archive button Notes
NVDA + Chrome “Archive, button, E” Read as part of the element’s properties
JAWS + Chrome “Archive, button” (shortcut on request with Insert+Tab / verbosity) Depends on verbosity settings
VoiceOver + Safari “Archive, button” then hint may include the shortcut Varies by version
TalkBack + Chrome Not announced Keep a visible or help-dialog route
Any, title tooltip Often read as description The title duplicates the shortcut for sighted mouse users
aria-keyshortcuts versus accesskey Comparison of the aria-keyshortcuts attribute, which only describes a shortcut, against the accesskey attribute, which makes the browser implement one. aria-keyshortcuts versus accesskey✓ aria-keyshortcutsDescribes a shortcut to assistive technologyYour code implements the keysAny key combination you chooseNo collisions you did not create✗ accesskeyBrowser implements the shortcutModifier differs by browser and OSCollides with browser and reader keysUsers cannot see which keys exist
aria-keyshortcuts is documentation; accesskey is implementation — and a risky one in applications.

Integration context

Permalink to "Integration context"

aria-keyshortcuts is one of three places a shortcut should appear, alongside a visible hint (tooltip or menu accelerator text) and the shortcut help dialog in keyboard shortcut help dialogs. Generating all three from one registry is what keeps them honest.

Single-character values like E still need SC 2.1.4 handling — a setting to turn them off or scoping — covered in single-character shortcuts and SC 2.1.4. When the setting turns them off, remove or update the attribute too.

One registry, three outputs Flow from a single shortcut registry to the keydown handler, the aria-keyshortcuts attributes on controls, and the help dialog listing. One registry, three outputsRegistrycommand → keysHandlermatches and runsAttributesaria-keyshortcutson controlsTooltips"Archive (E)"Help dialogfull, grouped list
If the handler, the attribute and the help are generated separately, they will disagree within a release or two.

Gotchas

Permalink to "Gotchas"

Mac modifiers. Write Meta for Command. If the shortcut differs by platform (Control+K versus Meta+K), set the attribute per platform rather than listing both.

Shift and character case. Shift+E and E are distinct in the ARIA syntax. If your handler lower-cases e.key, it treats them the same; decide and make the attribute match.

Conflicting reader keys. Insert and CapsLock are screen reader modifiers; avoid shortcuts that use them. Avoid Alt+letter combinations that open browser menus on Windows where possible.

Design system notes

Permalink to "Design system notes"

Buttons and menu items in the design system should accept a shortcut prop that sets aria-keyshortcuts, renders the accelerator text in menus, and appends it to tooltips — all from the same value. Pair it with a platform shortcut registry so the prop is populated from the registry rather than typed by hand in each product.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
Does aria-keyshortcuts make a keyboard shortcut work?

No. It only tells assistive technology that a shortcut exists. You must implement the key handling yourself and keep the attribute in sync with it.

Where should aria-keyshortcuts go?

On the element the shortcut activates or focuses — the Archive button, the search field — not on the grid or the page. That way the shortcut is announced when users reach the control.

How do I write multiple shortcuts in aria-keyshortcuts?

Separate alternatives with spaces, and join keys within one shortcut with plus signs, modifiers first — for example “Delete Control+Backspace”.

Should I use accesskey instead?

Generally no. accesskey makes the browser implement a shortcut with browser-specific modifiers that often collide with browser and screen reader commands. Implement shortcuts in script and describe them with aria-keyshortcuts.

Permalink to "Related"

← Back to Keyboard Shortcuts & Command Palettes