Three Ways to Give a Web App Real Keyboard Shortcuts on Windows
I’ve been moving a bunch of my little Windows tools into the browser lately. Progressive Web Apps, WebAssembly, the whole deal. No installer, works on my phone, updates itself. Pretty great.
Then I hit a wall.
I built a Pomodoro timer as a web app and wanted to hit a key to pause it while I was buried in Excel. Nope. The browser only hears the keyboard when the browser is the window in front. Switch to Excel and your web app is deaf.
That’s the sandbox doing its job. Web pages are NOT supposed to spy on your keyboard from the background. Good policy. Annoying for me.
Have you ever built something 95% of the way and then found out the last 5% was the part you actually cared about? Yeah, that.
So I spent a morning on it, asked a handful of AI models to poke holes in my plan, and ended up with three working approaches. Each one trades convenience for power. Now on with the show…
First, the plain-English picture
Think of your web app as someone in a soundproof room. They can hear you fine when you’re in the room with them (the browser is in front). Step outside and they hear nothing.
Unlock Global Hotkeys for Progressive Web Apps 🚀
The three approaches are three different ways of getting a message into that room from the hallway:
- Approach 1: Only talk when you’re already in the room. (Shortcuts that work while the browser is active.)
- Approach 2: Have the building manager relay a message. (A Chrome extension registers the shortcut.)
- Approach 3: Install a doorbell. (A tiny AutoHotkey helper on the PC listens and rings the page.)
Approach 1: Shortcuts while the browser is in front
This is the easy one and it’s free. A few lines of JavaScript listen for keys and call your app’s code. Ctrl+P, Alt+S, F2, whatever you like. If your app is WebAssembly, the JavaScript just passes the event along.
document.addEventListener(“keydown”, e => {
if (e.ctrlKey && e.key === “p”) {
e.preventDefault();
Timer.Toggle();
}
});
Good: nothing to install, any key combo, works in every browser on every device.
Bad: useless the moment you click into another program. For a timer you want to control from anywhere, that’s a deal breaker.
Approach 2: A Chrome extension with global shortcuts
This one surprised me. Chrome extensions get one privilege that web pages never get: they can ask Chrome to register a shortcut with Windows that fires even when Chrome is in the background. Chrome does the registering. No exe, nothing to download outside the browser.
You declare the shortcuts in the extension’s manifest and flag them as global:
“commands”: {
“toggle”: {
“suggested_key”: { “default”: “Ctrl+Shift+1” },
“description”: “Start or pause the timer”,
“global”: true
}
}
When the key fires, the extension’s background script sends a one-word message (“toggle”) to your app’s tab. If no tab is open, it opens one. Your app just listens for messages from that extension and runs the matching function.
Here’s the catch, and it’s a big one. Chrome only allows global shortcuts on Ctrl+Shift plus a digit, 0 through 9. Not Ctrl+P. Not Ctrl+Alt+anything. Ctrl+Shift+1, Ctrl+Shift+2, and so on. That’s a hard rule in Chrome. Ten shortcuts, take it or leave it.
A few more limits:
- Only four suggested shortcuts per extension. Users can add more by hand in Chrome’s shortcut settings.
- Chrome (or Edge) has to be running. Close the browser and the shortcuts die with it.
- Firefox has the extension API but not the global part. Safari doesn’t play at all.
- It’s a doorbell only. The extension can’t tell your app which program you were in, can’t paste text into Excel for you, can’t touch the clipboard.
Good: one-click install from the Chrome Web Store, no scary “Windows protected your PC” screen, updates automatically, one extension can serve all of your web apps.
Bad: Ctrl+Shift+digit or nothing.
Approach 3: A tiny AutoHotkey helper (the doorbell)
This is the one I actually shipped first, because global hotkeys are what AutoHotkey was born to do.
The idea: a very small program sits in the Windows tray. Its ONLY job is to listen for hotkeys and forward a one-word command to the web app over a local connection on the PC itself. The helper knows nothing about timers or tasks. It’s a wire. The web app owns all the logic.
The heart of it is embarrassingly short:
Hotkey(“^+p”, (*) => SendToPage(“toggle”))SendToPage(Cmd) {
; Snapshot the window the user was in, so the page can act on it later
Hwnd := WinGetID(“A”)
Json := ‘{“cmd”:”‘ Cmd ‘”,”hwnd”:’ Hwnd ‘}’
PushToBrowser(Json) ; local connection, 127.0.0.1 only
}
The real script is longer (pairing, a settings cache, a tray menu), but that’s the whole concept.
Some things I learned building it:
- No AutoHotkey install required. The helper compiles to a single 1.3 MB exe with the engine baked in. Users double-click it once.
- Pairing is one click. The web app opens a special link (like a mailto: link, but for the helper). Windows launches the helper, Chrome asks “Open HotkeyBridge?” once, and you’re connected. No typing codes.
- One helper serves every app. My timer, my recorder, whatever comes next. Each site pairs separately and gets its own hotkeys.
- The page picks the keys. There’s a “Change” button in the app’s settings. Press the new combo and the helper re-registers it live. Even the Win key works, which browsers can’t normally see.
- The helper remembers. Hotkeys stay registered between sessions. If a key fires and the app isn’t open, the helper opens it for you.
- Bonus power. Because it’s AutoHotkey, the helper can tell the page which window you were in. The page can later say “paste this into that window.” A pure web app can never do that.
Good: any key combo at all, works with the browser closed, can act on other programs, and it’s tiny.
Bad: it’s an exe. Unsigned exes get the SmartScreen warning on first run, and it’s Windows only.
Side by side
| Browser in front | Chrome extension | AutoHotkey helper | |
|---|---|---|---|
| Works from other programs | No | Yes, while Chrome is running | Yes, always |
| Key combos allowed | Anything | Ctrl+Shift+0 to 9 only | Anything, including Win key |
| User has to install | Nothing | An extension (one click) | A small exe (one download) |
| Browsers | All | Chrome, Edge | All (Windows only) |
| Can act on other windows | No | No | Yes |
| Best for | Everyone, as a baseline | Casual users, a few actions | Power users, Windows automation |
What I’d actually do
All three. They stack.
Approach 1 costs nothing, so every app gets it. Approach 2 is the easy on-ramp for people who just want to pause a timer without hunting for the window. Approach 3 is for the folks (like me) who want Win+F2 to do something useful in three different apps and don’t mind a tray icon.
The cool thing is the app itself doesn’t care which path the key came from. All three end up calling the same function. The plumbing changes, the app doesn’t.
One thing I’ve learned from this: the “browser can’t do that” wall is usually more like a fence. A tiny bit of native code on the right side of the fence, and suddenly a web app can do most of what a desktop app can. And you keep all the good parts of the web app: no installer for the main thing, phone support, instant updates.
How about you? Are you building things in the browser and hitting walls like this? Hit reply and tell me which wall. There’s a decent chance a small AutoHotkey script is the ladder. 😊
P.S. The helper approach is where I see the real opportunity. Keep the app in the browser, keep the native piece tiny, and let AutoHotkey do what it’s best at. If you want to learn AutoHotkey well enough to build these bridges yourself, our courses at the-Automator.com are the fastest way I know.















AHK Hero 🦸
⏩SHARE WITH A FRIEND⏩


