⌨️ Add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes
by sindresorhusSwift
Last 12 weeks · 4 commits
4 of 6 standards met
PRs are restricted to collaborators on this repo, so I'm filing this as an issue. Fixes are pushed to my fork and linked below — happy for them to be cherry-picked, or I can open a PR if you'd rather grant access. On macOS 26/27 the recorder is unusable: clicking the field focuses it, but pressing a shortcut types a character into the search field instead of recording it. ⌥⇧P inserts . The clear button does nothing at all. There are three independent defects that compound — each fix only changes the symptom, which is what made this hard to pin down. All three reproduce in a ~100 line SwiftUI app using nothing but this package, so it isn't app-specific. Verified on both 0.7.1 and 3.0.1. 1. holds its monitor token weakly https://github.com/ppardi/KeyboardShortcuts/commit/c3f86c6 returns an autoreleased object the caller must own until it's passed to . Held weakly, nothing retains it, so it's deallocated when the autorelease pool drains at the end of the current event-loop turn. arms its key monitor inside , so the monitor is gone before the user can press anything. The field looks focused and captures nothing. is unreachable for the same reason — it early-returns on a nil , so monitors are never removed either. Present since c5d135b (2021), evidently masked until now by AppKit keeping the token alive internally. 2. ends the recording session it just started https://github.com/ppardi/KeyboardShortcuts/commit/3f517c0 does this, in order: Mutating while the field editor is installed makes AppKit end and restart editing. The resulting calls unconditionally, and it can land after the monitor is armed — tearing it down microseconds later. The failure tracks the field's contents exactly, which is what makes it look random: field already holds a shortcut → → cell mutates → editing ends → monitor dies field empty → , already nil → no mutation → monitor survives So recording works on a cleared field and fails when replacing an existing shortcut. Clearing the field or refocusing appears to "fix" it because both leave it empty for the next focus. Trace — failing, then working: ``blur()` after a successful capture, so the user can keep trying combinations without clicking back in. That one is a UX preference, not a fix — it's deliberately last so the three fixes can be taken without it. Tested on macOS 27.
This adds support for SwiftUI menu items (they now auto update when shortcuts are changed). Also; it won't throw up a UI when modifying the existing shortcut. The implementation ... isn't great (it relies on comparing the instance points of NSMenuItems), but I can't find any other alternative where I can correctly identify that a SwiftUI generated NSMenuItem is related to a given shortcut. Added support for changeable SwiftUI .keyboardShortcuts, based on the work done by @mbenoukaiss Modified RecorderCocoa so that it doesn't show a dialog if we are reassigning one of our own menu items
Problem Shortcuts using (e.g. ) stop working intermittently. works reliably. Re-opening the app's settings window temporarily restores them. This was reported in a downstream app, FlashSpace, whose author directed me here. Root Cause I believe this is a fundamental conflict between Carbon's and the macOS Input Method Engine (IME). On macOS, combos (e.g. → ) are processed by the Text Services Manager before Carbon hotkey events are dispatched. As a result: → no character composition → fires reliably → triggers IME composition () → the event is consumed upstream and never reaches the Carbon event handler This doesn't always happen — it depends on the active application's IME mode, which is why the issue appears intermittently. Why the Settings Window "Fixes" It Looking at , when a menu is open, switches from to a with run loop mode (raw key events), which intercepts events before the IME layer. When the window closes, it switches back to , and the issue returns. Connection to I also noticed has an check for on macOS 15.0/15.1 in sandboxed apps — which suggests this is a known Apple regression. However, only affects the UI, not the actual hotkey registration, so the shortcut can still be registered but may silently fail to fire. Question Is there a way to use (or a ) permanently for shortcuts that involve , rather than only when a menu is open? That would allow intercepting events before the IME layer at all times. Or alternatively, could be broadened to cover more macOS versions or non-sandboxed apps, with a warning shown to users that shortcuts are unreliable? Environment macOS version: 26.3 Tested via FlashSpace app
Repository: sindresorhus/KeyboardShortcuts. Description: ⌨️ Add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes Stars: 2700, Forks: 248. Primary language: Swift. Languages: Swift (100%). License: MIT. Homepage: https://swiftpackageindex.com/sindresorhus/KeyboardShortcuts/documentation/keyboardshortcuts/keyboardshortcuts Topics: carthage, cocoapods, hotkey, keyboard-shortcuts, macos, swift-package, swift-package-manager, swiftui, swiftui-components. Latest release: 3.0.1 (2mo ago). Open PRs: 0, open issues: 2. Last activity: 2mo ago. Community health: 71%. Top contributors: sindresorhus, francisfeng, pvieito, Maschina, seungwoochoe, decodism, cweider, kant, DeLoeribas, gpoitch and others.