# @github/hotkey: Declaring Keyboard Shortcuts in HTML with data-hotkey

> @github/hotkey is a small JavaScript library from GitHub that binds keyboard shortcuts to elements through a data-hotkey attribute, and it is the mechanism behind the shortcuts on GitHub itself. It is easy to adopt, but its sequence handling and accessibility requirements set real boundaries.

**github/hotkey** — Trigger an action on an element with a keyboard shortcut.

- Repository: https://github.com/github/hotkey
- Website: https://github.github.com/hotkey/
- Stars: 3,300 · Forks: 99
- Language: JavaScript
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/github-hotkey

## What @github/hotkey solves, and who it is for

Most web apps implement keyboard shortcuts imperatively: a document-level keydown listener, a switch on event.key, and a growing pile of conditions about which element is visible. @github/hotkey replaces that with an attribute. A button carrying data-hotkey="Shift+?" becomes a shortcut target, and the library wires the key matching for you. The README states that all shortcuts within GitHub, including g i, ., and Meta+k, use hotkey to declare shortcuts in server side templates, and that it is used on almost every page on GitHub.

The intended audience is front-end and full-stack engineers who render markup on the server and want shortcuts to travel with that markup. Because the hotkey lives in HTML, a template author can add a shortcut without touching a central JavaScript key map. That is the whole value proposition, and it is a narrow one. If your shortcuts are already centralized in a client-side framework, this library mostly adds a second place to look.

## How install() maps keys to elements

The library reads the data-hotkey attribute from each element you pass to install. The README shows install(el) for every element matching [data-hotkey], and also install(el, el.dataset.shortcut) when you want to supply the hotkey string from a different attribute. Internally, the README describes a nested object keyed by the first key of a sequence: two-key sequences such as g c and g i are stored under the 'g' key with 'c' and 'i' as children.

That structure explains the library's central constraint. In the README's example, g c and c can both be available on the same page, but g c and g cannot coexist. When the user presses g, the c hotkey is unavailable for 1500 ms while the library waits to see whether g c or g i follows. This is a deliberate trade-off, not a bug, and it means a single-key shortcut that collides with the prefix of a sequence will feel laggy.

The hotkey string format follows UI Events key values. A bare key is the minimum. Commas separate aliases, so s,/ fires on either key. Spaces build sequences. Modifiers are joined with + and ordered as Control+Alt+Meta+Shift+KEY. The special Mod token resolves to Meta on MacOS and iOS and Control on Windows and Linux, and the README warns that Control or Meta must not appear alongside Mod in the same string. Plus and Space are named tokens for the + and space keys, and a comma key is written as an empty-looking alias, so a,, activates on a or on the comma. Shift must be written when the key is uppercase (Shift+A, not A), and the README notes that the automatic normalization of MacOS Meta+Shift output only works on US keyboard layouts.

## Installing @github/hotkey and wiring a first shortcut

The package is published on npm under the @github scope. The README gives the install command, and the package is ESM-first: package.json declares "type": "module" with dist/index.js as the entry point and a dist/index.d.ts type declaration. It is a build-time dependency, not a runtime service.

```bash
npm install @github/hotkey
```

After installing, import install and call it on the elements that carry the attribute. The README's loop is the shortest path to a working page.

```js
import {install} from '@github/hotkey'

for (const el of document.querySelectorAll('[data-hotkey]')) {
  install(el)
}
```

Markup then declares the shortcut directly. The README's examples cover a single character, aliases, a sequence, modifiers, and the Mod token.

```html
<a href="/page/2" data-hotkey="j">Next</a>
<a href="/search" data-hotkey="s,/">Search</a>
<a href="/rails/rails" data-hotkey="g c">Code</a>
<a href="/help" data-hotkey="Control+Alt+h">Help</a>
<a href="/settings" data-hotkey="Mod+s">Search</a>
```

What happens on a match depends on the element. Form elements such as input, textarea and select, plus anything with contenteditable, receive focus(). Everything else receives click(). Every element also emits a cancellable hotkey-fire event, so you can preventDefault and run your own handler, as the README demonstrates with a .frobber example. To remove a shortcut, call uninstall(el) on the same element. The README does not document any return value or error behavior for install or uninstall.

## The 1500 ms sequence wait and where it bites

The sequence buffer is the most consequential design decision in the library, and it deserves a direct look. Once a prefix key is pressed, the README says the single-key shortcut on that same key is unavailable for 1500 ms. On a page that binds g as a shortcut and also binds g c, the user who wants g waits a second and a half before anything happens. That is long enough to read as a broken key.

The practical consequence is a naming discipline: do not bind a single key and use it as a sequence prefix on the same page. The README states the incompatibility plainly for g c and g, so this is a documented boundary rather than an implementation surprise. If your product wants both, you have to change one of the two shortcuts.

There is a second boundary in the key normalization. The README says the automatic mapping of MacOS Meta+Shift output to uppercase only works on US keyboard layouts. On other layouts, a hotkey written as Mod+Shift+A may not match what the browser reports. The README does not offer a workaround, and the library has no documented remapping layer, so the mitigation is either to avoid Meta+Shift combinations or to test on the layouts you support. Neither is handled for you.

## Accessibility is a precondition, not a feature

The README is unusually direct here. It states that adding this functionality to your site can be a drawback for certain users, and that providing a way to disable or remap hotkeys makes sure those users can still use your site. It points to WCAG Success Criterion 2.1.4 on Character Key Shortcuts for the underlying requirement.

The library does not ship a remapping UI, a disable toggle, or a shortcut reference dialog. The README's first example, a button with data-hotkey="Shift+?" that shows a help dialog, is a pattern you would implement yourself. So the honest reading is that @github/hotkey gives you the key matching and nothing else. The compliance work sits with the integrator.

The second guideline is structural. The README asks that hotkeys be added to interactive and focusable elements wherever possible, and refers to the WAI technique for adding keyboard-accessible actions to static elements when a static element must be used. A div with data-hotkey and no role, no tabindex and no accessible name will fire on click() but will not be reachable by keyboard, which defeats the purpose of a keyboard shortcut. The library will not stop you from doing that.

## Alternatives and the difference in approach

The closest comparison is a general-purpose shortcut library such as Mousetrap or the shortcut layer inside a framework like Hotkeys-js. Those libraries typically expose a single register call that binds a key combination to a callback at the document level. The shortcut and the behavior live together in JavaScript, and the same registration works whether or not the target element is in the DOM.

@github/hotkey inverts that. The shortcut is an attribute on an element, and the action is whatever the element already does: focus for form controls, click for everything else. That is why it fits server-rendered templates. A Rails or Django view can declare data-hotkey="g i" next to the link it activates, and the shortcut disappears from the page when the link does. With a callback-based library, removing the link from the template leaves the registration behind unless you also clean it up.

The cost of the inversion is expressiveness. Callback libraries let one key trigger arbitrary logic and let you query and rebind the registry. @github/hotkey gives you one escape hatch, the hotkey-fire event, and otherwise assumes the element's default action is what you want. If your shortcut opens a modal that has no triggering element, or fires only in a particular application state, the attribute model fits poorly and you will end up with a hidden button that exists purely to be clicked.

## Maintenance, licence and what to check before adopting

The repository is not archived, and the last push was on 2026-09-11. The most recent releases listed are v3.1.4, v3.1.3 and v3.1.2, all published on 2026-03-16. The package is MIT licensed, which permits commercial and closed-source use with the usual requirement to preserve the licence notice. The README does not document a deprecation policy, a browser support matrix, or a migration guide between major versions. Note that the package.json in the repository root still declares version 2.0.0 while the release list shows 3.1.4, so treat the release tags, not that file, as the source of truth for what is published.

The upgrade surface is small. The public API described in the README is install, uninstall and the hotkey-fire event, and the package ships dist/index.js with dist/index.d.ts. A major version change is therefore likely to be visible in your code rather than hidden in transitive dependencies. The heavier cost is not the library but the shortcuts themselves: every key you bind is a key you must document, test on each supported layout, and make remappable for WCAG 2.1.4. That cost scales with your key map, not with the size of this package.

## Conclusion

Adopt @github/hotkey if you want declarative shortcuts in server-rendered HTML and can accept the sequence wait and the accessibility work it implies. Skip it if you need global shortcuts on non-focusable widgets, cross-layout key normalization, or a shortcut manager with conflict detection, since the README documents none of those. Before shipping, verify the 1500 ms sequence behavior against your own key map and check how Mod resolves on the platforms you target.

## FAQ

### How do I install @github/hotkey?

Install it from npm with npm install @github/hotkey, then import install and call it on the elements carrying the data-hotkey attribute. The package is ESM-first, with dist/index.js as the entry point and dist/index.d.ts for types.

### What is the 1500 ms delay in @github/hotkey?

When a key is the prefix of a two-key sequence, the README says a single-key hotkey on that same key is unavailable for 1500 ms while the library waits for the second key. In the README's example, g c and c can coexist on a page, but g c and g cannot.

### What does the Mod modifier mean in @github/hotkey?

Mod is a special modifier that localizes to Meta on MacOS and iOS and to Control on Windows and Linux. The README states that Control or Meta must not appear in the same hotkey string as Mod.

### What is a hotkey listener in @github/hotkey?

The library listens for key presses and matches them against the hotkey strings registered through install. When a hotkey fires, form elements and contenteditable elements receive focus(), other elements receive click(), and every element emits a cancellable hotkey-fire event.

### Does @github/hotkey handle accessibility for character key shortcuts?

No. The README warns that adding hotkeys can be a drawback for certain users and says providing a way to disable or remap them is what keeps the site usable, pointing to WCAG Success Criterion 2.1.4. The library itself does not ship a remap or disable mechanism.

## Sources

- [github/hotkey on GitHub](https://github.com/github/hotkey)
- [License: MIT](https://github.com/github/hotkey/blob/main/LICENSE)
- [Project website](https://github.github.com/hotkey/)
- [README](https://github.com/github/hotkey/blob/main/README.md)
- [Releases](https://github.com/github/hotkey/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/github-hotkey
