Every well known hotkey tool on the Mac keeps your keys in a database, a plist or an encrypted store, and gives you a window to click them into. Hotkeys keeps them in one file you can read, grep, diff and copy to the next Mac. This page is the whole argument, with the numbers behind it.
A key, a colon, a command. Comments sit next to the key they explain, macros keep long paths out of the way, and the file lives in your dotfiles like everything else you care about.
^f1: runApp("AppCode") # control f1
#l: runApp("Telegram") # command l
f4: osascript -e 'display dialog "Hi"'
At a hundred hotkeys the file is still the fastest interface you have. grep telegram ~/…/hotkeys.yml finds the key you half remember, your editor renames an app across forty lines in one pass, and git diff tells you what you changed last week. Save, press your reload key, and the new file is live without restarting anything. The tools that keep your shortcuts in a database, a plist or an encrypted store can offer you a scroll bar.
Three details you only notice after a week. A name that YAML would misread is quoted for you, so #k, !i and *f1 go in as they are, with no escaping trick. Doubling the hash, ##k, comments that one hotkey out and leaves it in the file. And the parser runs with every YAML type rule switched off, so 1:, 7: and f1: 007 mean exactly what they say and nothing quietly becomes a number, a boolean or a date.
BetterTouchTool keeps a versioned SQLite database and exports JSON that runs about 45 lines per shortcut, with UUIDs and timestamps that churn on every save. Keyboard Maestro keeps a plist its editor owns. Raycast keeps an encrypted store, and its export is encrypted too. None of them survives a grep, a code review or a merge.
A context is a line above the action. It matches on the bundle id, the process name or the window title, and else catches everywhere else. One key, every app it should behave differently in, all in one place.
f5:
currentAppChrome(): openMenuItem("Google Chrome", "View", "Reload This Page")
currentWindowContains("Emulator"): ADB shell input keyevent 187
else: runApp("Google Chrome")
Of the text configured tools only Hammerspoon matches window titles, and it takes an app watcher and about seven lines of Lua. Karabiner-Elements matches exact titles since 16.2 and about ten lines of JSON per rule. skhd, Keyboard Cowboy and Leader Key have no window titles at all, and Raycast hotkeys are global, they fire in every app or nowhere.
Bind displayActiveWindowInfo(), press it anywhere, and a panel prints 14 facts about the window in front: bundle id, process name, pid, both readings of the title, layer, alpha, resident memory and exact bounds. Select the line, paste it into your config, done. It is the difference between guessing a bundle id and knowing it.
Twelve of those fourteen need no permission at all. The same block goes to the log, so a support mail is a copy and paste. Every rival either hides this in a settings window, shows key codes only, or leaves you installing a third-party window inspector to write its config.
A matched hotkey is consumed, unless you say otherwise. forward: true runs your action and hands the key to the app in front anyway, per hotkey. If a key has contexts and none match, it passes through untouched.
#s:
forward: true
shellScript: ~/bin/backup-notes.sh # save in the app, back up as well
Keyboard Maestro always swallows its hot key. BetterTouchTool has the switch for key sequences, not for a plain shortcut. Hammerspoon does it with a disable, retype, enable trick.
Commands run in your login shell, so the PATH from your profile is there and brew, pyenv and your own scripts work without full paths. AppleScript goes inline or as a block. Menu items are clicked by name, not by screen coordinates. Keystrokes go to the app in front, and a key can flip the input source.
This is where the others cost you an afternoon. BetterTouchTool runs /bin/bash without your profile, Keyboard Maestro a /bin/sh with four directories on PATH, Karabiner a /bin/sh that knows only HOME, USER and UID, Alfred a fixed PATH, Raycast a non login shell. Their forums are full of command not found.
114 physical keys are nameable: F1 to F20 without a modifier, the numpad including its enter, punctuation written as the character itself, arrows, home and end, and the four modifiers in any mix. A hotkey can even be modifiers alone: !* is Option and Shift pressed and released, which is how the keyboard layout gets switched here, the way Alt and Shift do it on Windows. Names follow the key position, not the letter printed on it, so a hotkey survives a switch to another layout.
The notation is AutoHotkey's, ^ Control, ! Option, # Command, with * for Shift, so a Windows config translates almost line by line.
A password field, Terminal's Secure Keyboard Entry or a password manager switches on secure input, and macOS then keeps keystrokes away from every watching app. Most hotkey tools go quiet. Hotkeys turns its menu bar icon into a lock, names the app holding it, and keeps firing: every combination with Command or Control, Option combos included, and modifier‑only keys. An optional helper, off until you turn it on, adds F‑keys, Esc, Tab, Delete and the arrows.
On a real 107-hotkey config, 83 keys keep firing with no extra permission at all, and 102 with the helper switched on.
One thing cannot work there: a key that types a character, pressed without Command or Control. macOS gives those to nothing but a keyboard driver, which is why Karabiner-Elements is the one tool that takes them. skhd and Keyboard Cowboy stop completely. How it works.
macOS switches off any event tap whose callback runs too long, and every hotkey in every tool built this way goes dead until it is relaunched. Hotkeys catches that in the same callback, switches the tap back on and re-reads which modifiers you are holding.
A typo never disarms your keyboard either. Reload a config with a syntax error and the hotkeys you already had keep firing, while the dialog names the line, gives the reason and prints the line back to you. Write the same key twice under two spellings, cmd-k and #k, and both are named at load instead of one silently winning. A key whose contexts do not match is not swallowed, it reaches the app in front exactly as if Hotkeys were not running. And there is a window inspector on a key: displayActiveWindowInfo() prints 14 facts about whatever is in front, bundle id, process, pid, both title readings, memory and bounds, in a panel you select and paste straight into your config.
Swift and a CGEvent tap, no Electron, no embedded Lua or Node, no kernel extension and no login account. The download is 5.2 MB against 90 MB for BetterTouchTool, 122 MB for Raycast, 42 MB for Karabiner-Elements and 37 MB for Keyboard Maestro, and it sits at 28 MB of memory and 0.09% of one core while it waits for a key.
8,034 lines of Swift, three frameworks in the bundle, one entitlement in the whole entitlements file, two processes and three threads at rest. Small enough that you can audit what it asks the system for in one command.
No account, no analytics, no crash reporter. Release builds never write a key name or the front app's bundle id to the system log, so there is no record of what you pressed even on your own Mac. The update check fetches a 778-byte file and sends no model, CPU, memory, macOS version or language with it. A free copy never contacts the licence server at all, and an activated one sends four fields, none of them a hardware fingerprint. Your config is a file on your disk that nothing on the network ever reads.
The optional password field helper is built to the same rule. It opens the keyboard non-seizing, so it has no mechanism to swallow a key. Its entire vocabulary back to the app is one number out of a list the app itself wrote, never a key code, a character or a timestamp. A key that types a character is watchable only together with Command or Control, and that rule is enforced three times: in the app, in the helper when the list arrives, and again on every press. It holds zero entitlements, and it answers only the user sitting at the Mac.
brew install --cask hotkeys-app/tap/hotkeys
Or the notarized, stapled dmg, which updates itself daily from its own signed feed, and installing it never asks for an administrator password. There is an App Store edition too, which none of BetterTouchTool, Keyboard Maestro, Karabiner-Elements, Hammerspoon, skhd, Raycast or Alfred offers, and moving up from it carries your config out of the sandbox on first launch, with nothing to export.
Leaving is as easy: brew uninstall --zap --cask hotkeys removes the app and names every path it ever wrote, and the cask unloads the password field helper, which is a root service that would otherwise outlive the bundle.
Free is the whole product: every method, every context, every trigger, no clock and no countdown. The only thing a licence changes is the number of hotkeys the file loads. Pro is 15 dollars once, three Macs, updates included, bought in ten seconds without an account. BetterTouchTool is 25 lifetime after a 45 day trial, Keyboard Maestro 36 per major version after 30 days, the Alfred Powerpack 34 pounds, Raycast Pro 96 dollars a year, every year.
Command K opens Telegram, but only while Xcode is in front. Everywhere else the key goes to the app untouched. A macro carries the context, so the hotkey itself stays one readable line: XCODE: currentApp("com.apple.dt.Xcode"), written once, used by every key that cares about Xcode.
cmd-k:
XCODE: runApp("Telegram") # XCODE is a macro, written once at the top of the file
cmd - k [
"xcode" : open -a Telegram
]
local tg = hs.hotkey.new({"cmd"}, "k", function() hs.application.launchOrFocus("Telegram") end)
xcodeWatch = hs.application.watcher.new(function(_, ev, app)
if ev == hs.application.watcher.activated then
if app:bundleID() == "com.apple.dt.Xcode" then tg:enable() else tg:disable() end
end
end):start()
The three skhd lines also do less: it matches the process name, never the bundle id, and it has no window titles and no menu items, so the same key cannot tell two Chrome windows apart or click a menu for you. Karabiner-Elements takes about ten lines of JSON for the same rule, one line with the third party Goku DSL. BetterTouchTool, Keyboard Maestro, Alfred and Keyboard Cowboy can only be clicked together in their editors. Raycast and Leader Key cannot scope a hotkey to one app at all.
Only rows that are true today. Where a tool is stronger it says so further down.
| Hotkeys | BetterTouch Tool | Keyboard Maestro | Karabiner | Hammer spoon | skhd | Raycast | |
|---|---|---|---|---|---|---|---|
| A hotkey is one line of plain text | ✓ | — | — | ~10 of JSON | Lua | ✓ | — |
| Find and change any hotkey with grep | ✓ | — | — | ✓ | ✓ | ✓ | — |
| The same key, a different job per app | ✓ | ✓ | ✓ | ✓ | in code | ✓ | — |
| Match on the window title | ✓ | ✓ | ✓ | exact only | ✓ | — | — |
| Unmatched key passes through untouched | ✓ | ✓ | ✓ | ✓ | in code | ✓ | — |
| Run the action and still pass the key | ✓ | sequences | — | ✓ | workaround | ✓ | — |
| Shell commands get your login PATH | ✓ | — | — | — | opt in | fork only | — |
| Click a menu item by name | ✓ | ✓ | ✓ | — | ✓ | — | interactive |
| Send keystrokes, switch input source | ✓ | ✓ | ✓ | ✓ | ✓ | keys only | partly |
| Modifier‑only hotkeys, option+shift alone | ✓ | ✓ | workaround | ✓ | in code | — | ✓ |
| Edit the file, reload from a hotkey | ✓ | — | — | ✓ | ✓ | ✓ | — |
| A window inspector on a key | ✓ | in settings | in the editor | keys only | in the console | — | — |
| Hotkeys inside password fields | ✓ | partly | partly | ✓ | partly | — | partly |
| Option combos inside password fields | ✓ | — | — | ✓ | — | — | — |
| No driver, no account, no analytics | ✓ | usage stats | version check | driver | crash reports | ✓ | — |
| Install and update with Homebrew | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| An App Store edition | ✓ | — | — | — | — | — | — |
| Download | 5.2 MB | 90 MB | 37 MB | 42 MB | 9.7 MB | source | 122 MB |
| Price | FreePro 15 once, three Macs | 25 lifetime45 day trial | 36 per version30 day trial | free | free | free | freePro 96 a year |
| Releases in 2026 | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ |
Read from each vendor's docs, release notes, forums and GitHub in September 2026: BetterTouchTool 6.845, Keyboard Maestro 11.1.1, Karabiner-Elements 16.3.0, Hammerspoon 1.1.1, skhd 0.3.9, Raycast 2.4. Something out of date? Tell us and it gets fixed.
This app does keys. Gestures, clipboard history, macros with loops, window managers and launchers are other jobs, and every one of them runs happily next to it. Here is where each lives, and what happens to your keys if you let that app hold them too.
BetterTouchTool, and keep it. Just leave your keys here. Its shortcuts live in a database behind a settings window with no text search, and the number of hotkeys only ever grows: at ten it is pleasant, at a hundred you are scrolling a list you cannot grep, diff or paste to a colleague.
That is a programming environment, and Keyboard Maestro is the one. Keys that launch an app, run a script or click a menu do not need it: here they are one line each, and they stay in a file you can read a year later.
That is remapping, one layer below this, and Karabiner-Elements does it. Run both, plenty of people do: it decides what a key is, Hotkeys decides what the key does in the app in front. Ask and we will take the requests that make sense here too.
Hammerspoon, with window managers, watchers for Wi‑Fi and USB and 136 Spoons. That is code you maintain. This is configuration: the scripts stay in your own files and the keys stay in one line each.
Raycast or Alfred, and keep the one you like. Their hotkeys start their own commands, Raycast's are global by design, so a key cannot mean two things in two apps. Hotkeys owns the keys that depend on what is in front of you, and the two never collide.
Same idea, still maintained. Its last release is May 2023 with nothing in 2026, and it matches process names only: no bundle ids, no window titles, no menu items, no menu bar app, no signed updates, and you build it yourself. Your .skhdrc lines translate almost one to one.
Both are big GUI apps, BetterTouchTool for gestures, mouse and windows, Keyboard Maestro for macros with logic. Hotkeys does keys only and keeps them in one YAML file, readable, diffable, easy to keep in git. A hotkey can run its action and still pass the key to the app, which neither does for a plain shortcut, and shell commands see your PATH.
Karabiner remaps keys, caps lock to escape, tap versus hold, layers, and it is free and excellent at it. Running commands per app is where it gets heavy: about ten lines of JSON per hotkey, a bare /bin/sh without your PATH, and no menu items. Hotkeys does that part in one line. Plenty of people run both.
The closest thing there is. The modifier symbols are AutoHotkey's, ^ Control, ! Option, # Command, with * for Shift. Per app keys work like #HotIf, sendKeys like Send, openMenuItem like MenuSelect, reloadConfig() like Reload. There are no hotstrings and no scripting language, the logic lives in the scripts you call.
It can, in Lua. Limiting Command K to Xcode is an app watcher and about seven lines, and the key is taken from every other app unless your code turns the hotkey off. Here it is two lines and the key passes through everywhere else.
For most configs, yes. The lines are as short and the ideas are the same, per app keys and pass through. On top there are window title contexts, menu items by name, keyboard layouts, a menu bar app and signed updates. Modes are the one thing skhd has that Hotkeys does not.
Keep them. Raycast hotkeys are global and Alfred's start workflows. Hotkeys adds the keys that do a different job per app and window and pass through untouched everywhere else.
Partly. System Settings, Keyboard, Keyboard Shortcuts, App Shortcuts re-binds an existing menu item per app. The Shortcuts app can put a global key on a shortcut. Neither gives one key a different job per app, runs a command only in one app, or lives in a text file.
A settings window is the easiest way to add one shortcut and the hardest way to keep a hundred. A file can be read top to bottom, searched, commented, diffed, copied from a colleague and restored from git.
It does one thing. Ten hotkeys are free with no clock, the rest is 9 dollars a year or 15 once, on three Macs. BetterTouchTool is 25 lifetime, Keyboard Maestro 36 per major version.
Every feature is in the free tier, a license only lifts the ten key limit. macOS 14 Sonoma or later.