hotkeys-js
Zero-dependency JavaScript library for binding, scoping, and dispatching keyboard shortcuts in the browser.
Repository Health
Technical Analysis
hotkeys-js is a small, dependency-free library for capturing keyboard input in the browser. A single global hotkeys() function binds one or more comma-separated key combinations to a callback, with support for scopes, modifier keys, keyup/keydown control, and per-element binding, while a companion API object (setScope, unbind, trigger, isPressed, getPressedKeyCodes) covers everything else an app needs to manage shortcuts at runtime.
Rewritten in TypeScript for its 4.0 release, it ships as ESM, UMD/CommonJS, and a minified browser IIFE bundle from a single Vite build, and weighs in around 8kB (3.8kB gzipped). It has no dependencies of its own and is designed to not interfere with any other JavaScript library on the page, including a coexisting global named hotkeys via noConflict().
What You Get
- A single
hotkeys(key, options?, handler)call to bind one or more comma-separated key combinations to a callback, with independent keyup/keydown control. - Scope management (
setScope,getScope,deleteScope) so multiple UI contexts can reuse the same key combos without colliding, with'all'always active alongside the current scope. - A full unbind API (
unbind) supporting a single key string, arrays of{key, scope, method}descriptors, or a global unbind-everything call. - Prebuilt ESM, UMD/CJS, and minified IIFE bundles plus generated TypeScript declarations, so it drops into any bundler or a plain
<script>tag.
Common Use Cases
- Command palettes & keyboard-first UIs - binding single-key and modifier shortcuts for quick actions without hand-rolled
keydownlisteners. - Scoped multi-context apps - swapping the active scope so the same key does different things in different views, such as a list view versus a detail view.
- Cross-platform modifier shortcuts - registering
command+r, ctrl+rin one call so Mac and Windows/Linux users each get the platform-appropriate modifier. - Embedding via CDN - dropping the minified IIFE build straight into a
<script>tag for simple pages with no build step.
Under The Hood
Architecture
The entire runtime lives in three small modules: src/index.ts holds all binding/dispatch/unbind logic, src/var.ts holds the shared mutable key maps and the _handlers registry, and src/utils.ts holds pure helper functions (event add/remove, modifier parsing, key-string splitting, layout-independent key resolution). There’s no class hierarchy or dependency injection — it’s a closures-over-module-state design, where _handlers, _downKeys, _mods, and a per-element listener Map are module-level bindings closed over by hotkeys(), dispatch(), eventHandler(), and unbind(). Binding pushes descriptor objects (key, scope, mods, method, element) into _handlers[keyCode]; native keydown/keyup events drive dispatch(), which matches the currently pressed-key set against those descriptors and invokes the stored method. Because dispatch, unbind, and getAllKeyCodes all reach directly into the same shared _handlers object, any change to its shape has to be threaded through each of them individually — there’s no encapsulation boundary, though the surface area is small enough that this stays manageable.
Tech Stack
Written in strict-mode TypeScript (ES2015 target, DOM lib) and built with Vite plus vite-plugin-dts, producing ESM (hotkeys-js.js), UMD/CJS (hotkeys-js.umd.cjs), and a minified browser IIFE (hotkeys-js.min.js) declared through package.json’s exports map, with zero runtime dependencies. The website/ directory is a separate React 19 + Vite documentation/demo app (using the maintainer’s own @uiw/react-* component packages) that’s built independently and published to GitHub Pages; it isn’t part of the shipped library. Testing runs on Jest 29 with jest-environment-jsdom, plus a Puppeteer-driven real-browser suite that loads a static test page and collects V8 coverage reported to Coveralls. Husky and lint-staged run ESLint’s flat config (typescript-eslint plus react/jsx-a11y/import plugins for the docs app) on every commit.
Code Quality
Tests combine jsdom-based Jest specs with genuine browser-driven Puppeteer tests that dispatch synthetic KeyboardEvents against the built IIFE bundle, with coverage collected via page.coverage and reported through a Coveralls badge in the README. Naming is consistent camelCase, with underscore-prefixed identifiers (_handlers, _downKeys, _mods) signaling module-private state. TypeScript strict mode is fully enabled (strict, strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply all on), and GitHub Actions runs CI on every push and pull request. Error handling is minimal by design — this is a synchronous UI-event library rather than an I/O boundary, so functions guard with type checks rather than throwing.
What Makes It Unique
The most distinctive piece is getLayoutIndependentKeyCode in src/utils.ts: it prefers event.key for Latin letters (so alternate layouts like Dvorak or Colemak match the typed character) but falls back to the physical event.code for KeyA-KeyZ when the typed character isn’t Latin, so combinations like ctrl+m keep working on Cyrillic or Greek keyboards. Alongside that sit several hard-won, comment-documented cross-browser fixes baked directly into the dispatch loop: special handling that clears all pressed keys when the Meta/Command key is released (since browsers never fire keyup for other keys while Command is held), and a fullscreenchange listener added specifically to clear stuck keys because keyup events can be lost when a shortcut like Alt+F triggers fullscreen. None of this is architecturally novel, but the depth of real-world keyboard-quirk handling is notably more thorough than most comparable libraries.
Used by 4 apps in this directory
NoteGen
Note Taking · Knowledge Management · AI Assistants
Capture anything first, then let AI transform your scattered records into polished, structured Markdown notes with RAG-powered knowledge retrieval.
Notesnook
Note Taking · File Storage · Security
End-to-end encrypted, open-source note-taking where your data stays yours — even from the server.
Operately
AI Assistants · Project Management · Productivity
The open source company operating system that unifies OKRs, projects, and team execution with built-in accountability cadences.
Weblate
Developer Tools
Continuous localization platform that commits translations directly into your version control system with full translator attribution.