Open-source project
sindresorhus/KeyboardShortcuts avatar
sindresorhus/KeyboardShortcuts

KeyboardShortcuts: user-customizable hotkeys for macOS apps in Swift

⌨️ Add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes

2,712 stars253 forksSwiftMIT

At a glance

What is it?
A Swift package that gives a sandboxed macOS app a recorder view, UserDefaults storage and a key-up listener for global shortcuts. It is a small library with a narrow job, and it does that job without an event tap.
Who is it for?
Adopt KeyboardShortcuts if you are shipping a sandboxed macOS app in SwiftUI or AppKit and you want users to choose their own global hotkeys without writing an event tap or a recorder UI. Do not adopt it if you need cross-platform shortcuts, per-window local key handling, or a shortcut model that survives outside UserDefaults.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 18 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: global hotkeys in a sandboxed macOS app

A macOS app that wants a global hotkey has two jobs that look simple and are not. The first is capturing the key combination when the app is not frontmost. The second is letting the user pick that combination, showing them what they picked, and warning them when the system or the app's own menu bar already owns it. Doing the second job badly is worse than not doing it: a recorder that silently accepts Command-Q or a combination macOS already reserves produces an app that appears broken.

KeyboardShortcuts targets that pair of jobs. The README describes it as adding "support for user-customizable global keyboard shortcuts to your macOS app in minutes," and states that it is fully sandboxed and Mac App Store compatible. The sandbox point is the load-bearing one. A library that needs to install a CGEventTap or an accessibility permission prompt cannot ship in a sandboxed App Store build without extra entitlement friction, and the README's claim is that this package avoids that.

The audience is narrow and specific: a developer writing a SwiftUI or AppKit app for macOS 10.15 or later, who wants the user to be able to rebind a hotkey. The README names four shipping apps that use it, Dato, Jiffy, Plash and Lungo, which is the kind of evidence that matters here because it means the sandbox claim has been exercised in real App Store submissions.

How a Name, a Recorder and an event stream fit together

The design has three moving parts and the separation between them is the whole point.

The first is KeyboardShortcuts.Name, a strongly typed identifier. You declare it once as a static property in an extension, and from then on the rest of the API takes that value instead of a string. The README's example is a static let called toggleUnicornMode, initialized with Self("toggleUnicornMode"). The string is the storage key. The static property is the compile-time handle.

The second is the recorder, a SwiftUI view (KeyboardShortcuts.Recorder) or an AppKit control (KeyboardShortcuts.RecorderCocoa). You give it a label and a name. The README states that the recorder stores the shortcut in UserDefaults and warns the user if the chosen combination is already used by the system or the app's main menu. That warning is the feature most hand-rolled recorders skip, and it is the reason to use this package rather than a text field that captures key codes.

The third is the listener. KeyboardShortcuts.onKeyUp(for:) takes a name and a closure, and the README notes that onKeyDown is also available. Newer releases add repeatingKeyDownEvents(for:), which the README describes as emitting once on the initial press and then repeating using the system key repeat settings, on macOS 13 and later. That last detail matters: the repeat cadence is not your app's choice, it follows whatever the user configured in System Settings, which is the correct behavior and also means you cannot tune it.

There is an escape hatch for shortcuts that should not be user-editable. You can construct a KeyboardShortcuts.Shortcut directly and iterate KeyboardShortcuts.events(for:). The README's own advice on that path is blunt: "Prefer user-customizable shortcuts whenever possible."

Installing it with Swift Package Manager and wiring a first shortcut

The README gives one install route: add the repository URL in the Swift Package Manager tab in Xcode. There is no separate CLI install step documented, and no CocoaPods or Carthage command in the README body, even though the repository carries both as topics.

The first thing to write is the name. Put it in its own file so the rest of the app can refer to it without importing anything unusual.

swift
import KeyboardShortcuts

extension KeyboardShortcuts.Name {
	static let toggleUnicornMode = Self("toggleUnicornMode")
}

The string argument is what ends up in UserDefaults, so treat it as a stable identifier and do not rename it after shipping.

Next, build a settings screen with the recorder. This is the entire UI.

swift
import SwiftUI
import KeyboardShortcuts

struct SettingsScreen: View {
	var body: some View {
		Form {
			KeyboardShortcuts.Recorder("Toggle Unicorn Mode:", name: .toggleUnicornMode)
		}
	}
}

When the user clicks the recorder and presses a combination, the field should display that combination, and if the combination collides with a system or menu shortcut, the recorder is documented to warn about it.

Finally, attach the handler. The README's example does this from an @MainActor @Observable class initialized at app start.

swift
import KeyboardShortcuts

KeyboardShortcuts.onKeyUp(for: .toggleUnicornMode) {
	isUnicornMode.toggle()
}

After that, pressing the recorded combination while another app is frontmost should run the closure. The README points to a complete example in the Example directory and to a real-world usage in Plash's PreferencesView.swift if you want to see the pattern in a shipping app.

Where the package stops: initial shortcuts, dynamic names and platform limits

The README is explicit about one thing most libraries leave to the developer's conscience. You can set an initial shortcut by passing an initial value to the Name, and the README asks you not to do it for a publicly distributed app: "Users find it annoying when random apps steal their existing keyboard shortcuts." That is a real design constraint, not a style note. It means the first-run experience for a distributed app is a welcome screen or an empty recorder, and the package will not do that part for you.

The second constraint is that names can be created dynamically. The static extension pattern is described as convenience for dot-syntax, not a requirement. If your app has user-defined actions, you create the Name values yourself and store them. The package does not manage that collection, and the README's suggestion for enumerating everything is to conform KeyboardShortcuts.Name to CaseIterable and write allCases by hand. That works for a fixed set and becomes awkward the moment the set is user-generated, because allCases is a static array.

The third is the platform floor. Requirements say macOS 10.15 or later, and repeatingKeyDownEvents(for:) is marked macOS 13 or later. There is no iOS, iPadOS or Windows target here. The related searches for Windows and iPhone shortcuts have nothing to do with this package; it is a macOS-only Swift library.

One more limitation worth naming plainly: storage is UserDefaults. The README states the recorder takes care of storing the shortcut there. That is convenient and it is also the boundary. If you need shortcuts synced across devices, or stored in a document, or versioned alongside a user profile, the package does not offer that and you would be reading and writing the same keys yourself.

KeyboardShortcuts compared with HotKey and hand-rolled NSEvent handling

The obvious alternative in the same ecosystem is HotKey, a small Swift wrapper around Carbon's RegisterEventHotKey. The difference in approach is the layer they occupy. HotKey gives you a hotkey registration primitive: you supply a key code and modifiers, and you get a callback. It does not give you a recorder view, it does not store anything in UserDefaults, and it does not know about your app's menu bar, so the conflict warning the README describes for KeyboardShortcuts.Recorder has no equivalent there. If you already have your own settings UI and your own persistence, HotKey is the smaller dependency. If you do not, you will be writing the recorder and the storage layer yourself, and the conflict detection is the part that is easy to get subtly wrong.

The other alternative is doing it directly with NSEvent monitors or a Carbon hotkey registration inside your own app. That is viable, and it is what many apps did before this package existed. The cost is the same as above plus the sandbox question, which the README answers for KeyboardShortcuts by stating it is fully sandboxed and Mac App Store compatible. Whether your own implementation clears that bar is something you have to determine yourself.

A third comparison is worth making for scope. KeyboardShortcuts is not a general key-binding system for in-window commands, and it is not a menu shortcut manager. The README does mention an NSMenuItem helper for showing a recorded shortcut in a menu item, which is a display concern, not a binding one. If your problem is local shortcuts inside a focused window, the standard responder chain and NSMenuItem key equivalents are the right tool, and adding this package would be a detour.

Maintenance, versioning and the MIT licence

The repository is not archived and the last push was on 2026-09-11, the same day as the 3.1.0 release. The releases before that are 3.0.1 on 2026-06-17 and 3.0.0 on 2026-06-14, so the 3.x line is recent and the 3.0.0 major bump landed a few months before 3.1.0. A major version bump is the upgrade signal to watch: if you are on a 2.x pin, the 3.0.0 release is where you should read the release notes before moving, and the README does not document a migration path from earlier major versions. That is a gap. Nothing in the README explains what changed in 3.0.0 or whether UserDefaults keys written by an older version are still readable.

The licence is MIT, which permits use in closed-source and commercial apps and requires that the copyright notice and permission notice be included. The README does not discuss attribution requirements beyond the repository licence file, and this is not legal advice; read the licence text in the repository if your distribution model depends on the details.

Upgrade cost is otherwise low by construction. The public surface is a Name type, two recorder views, a handful of event methods and a Shortcut type. The README's own framing is that it implements what the author needed for his apps and that PRs are welcome, which suggests the feature set grows by contribution rather than by roadmap. Plan for the API you see, not for one you expect.

Editorial conclusion

Adopt KeyboardShortcuts if you are shipping a sandboxed macOS app in SwiftUI or AppKit and you want users to choose their own global hotkeys without writing an event tap or a recorder UI. Do not adopt it if you need cross-platform shortcuts, per-window local key handling, or a shortcut model that survives outside UserDefaults. Before you commit, verify on a real machine that your chosen default modifier combination is not already claimed by the system, and check that KeyboardShortcuts.Recorder's conflict warning appears for the combinations your app ships with.

Frequently asked questions

How do I install KeyboardShortcuts in an Xcode project?

The README gives one route: add https://github.com/sindresorhus/KeyboardShortcuts in the Swift Package Manager tab in Xcode. There is no separate command-line install step documented in the README.

What macOS versions does KeyboardShortcuts support?

The requirements section states macOS 10.15 or later. The repeatingKeyDownEvents(for:) API is separately marked macOS 13 or later in the README.

Does KeyboardShortcuts work in a sandboxed Mac App Store app?

The README states that the package is fully sandboxed and Mac App Store compatible, and lists Dato, Jiffy, Plash and Lungo as production users.

Where does KeyboardShortcuts store the shortcut the user picks?

The README states that KeyboardShortcuts.Recorder takes care of storing the keyboard shortcut in UserDefaults. The package does not document any other storage backend.

Can I set a default keyboard shortcut for my app?

Yes, by passing an initial value to KeyboardShortcuts.Name, but the README advises against doing this for a publicly distributed app because users find it annoying when apps take over existing shortcuts.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. sindresorhus/KeyboardShortcuts on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sindresorhus-keyboardshortcuts.svg)](https://hysenlabs.com/projects/sindresorhus-keyboardshortcuts)