Fast, deterministic keyboard-layout switching on macOS — powered by Hammerspoon.
macOS only lets you cycle through input sources, and its "switch to the last one" shortcut guesses which layout you mean (badly, if you use more than two). This gives every layout its own permanent hotkey, plus keeps the familiar one-key toggle:
- 👆 Tap
CapsLock→ toggle between your two most recent layouts - ⌨️ Hold
CapsLock+ a letter → jump straight to a specific layout - 🆔 Hold
CapsLock+0→ show & copy the current layout's source ID
Switching is instant and predictable — no more "why is it Korean again?".
Why
CapsLock? It's under your finger and almost nobody uses it for ALL-CAPS (holdShiftinstead). We repurpose it into a clean modifier key.
| Shortcut | Layout |
|---|---|
CapsLock tap |
toggle two most recent |
CapsLock + E |
English |
CapsLock + R |
Russian |
CapsLock + K |
Korean |
CapsLock + C |
Chinese |
CapsLock + A |
Armenian |
CapsLock + D |
German (Austrian) |
CapsLock + 0 |
show/copy current layout ID |
These are just examples — you pick your own layouts and letters (see Configure your layouts).
- macOS (tested on Apple Silicon, recent macOS)
- Hammerspoon (the installer sets it up for you)
git clone https://github.com/aredancing/mac-layout-switcher.git
cd mac-layout-switcher
./install.shThe installer will:
- Install Hammerspoon (via Homebrew, if it isn't already installed).
- Copy
init.luato~/.hammerspoon/(backing up any existing one). - Remap
CapsLock → F18now and persistently (a small LaunchAgent). - Launch Hammerspoon.
Then finish two manual steps macOS requires:
- Grant Accessibility — Hammerspoon will prompt you, or go to System Settings → Privacy & Security → Accessibility and enable Hammerspoon. (Required for global hotkeys.)
- Click the Hammerspoon menu-bar icon (🔨) → Reload Config.
- (Optional) In Hammerspoon Preferences, tick Launch Hammerspoon at login so the hotkeys survive a reboot.
If you'd rather not run the script
# 1. Hammerspoon
brew install --cask hammerspoon
# 2. Config
mkdir -p ~/.hammerspoon
cp init.lua ~/.hammerspoon/init.lua
# 3. Remap CapsLock -> F18 (this session)
hidutil property --set \
'{"UserKeyMapping":[{"HIDKeyboardModifierMappingSrc":0x700000039,"HIDKeyboardModifierMappingDst":0x70000006D}]}'
# 4. Make the remap persistent
cp com.user.capslock-f18.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.user.capslock-f18.plistThen grant Accessibility to Hammerspoon and Reload Config (see above).
The layouts table at the top of init.lua maps a letter → a layout.
Every entry is ["<letter>"] = { "Display name", "<sourceID>" }.
You need the source IDs of the layouts you have enabled. Two ways to find them:
A. List them all (recommended). Build and run the tiny helper:
clang -framework Carbon -framework Foundation -o /tmp/list-input-sources tools/list-input-sources.m
/tmp/list-input-sourcesIt prints your enabled input sources, e.g.:
ABC com.apple.keylayout.ABC
Russian – PC com.apple.keylayout.RussianWin
2-Set Korean com.apple.inputmethod.Korean.2SetKorean
B. One at a time. Switch to a layout manually (menu bar), then hold
CapsLock + 0 — it shows the current layout's ID and copies it to your
clipboard.
Then edit ~/.hammerspoon/init.lua, e.g.:
local layouts = {
["e"] = { "English (ABC)", "com.apple.keylayout.ABC" },
["u"] = { "Ukrainian", "com.apple.keylayout.Ukrainian-PC" },
["j"] = { "Japanese", "com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese" },
}Save, then Hammerspoon menu-bar icon → Reload Config. Done.
Tuning the tap:
TAP_MAX(default0.4s) is how long a press can be and still count as a "tap". If quick taps sometimes get missed, raise it to0.5.
- CapsLock as a modifier. CapsLock is a locking toggle, not a normal
modifier, so "CapsLock + key" isn't possible directly. We remap it to the
otherwise-unused F18 key with
hidutil, giving it clean key-down/key-up behavior. Hammerspoon then treats "F18 held" as a modal modifier. - Reliable switching.
hs.keycodes.currentSourceID()sometimes updates the menu-bar icon but not the actual typing on the first try, so we set it twice. - Leaving an IME. Going from an input method (Korean, Chinese, …) to a plain layout, macOS won't release the IME until a different source is selected. The config automatically "bounces" through an intermediate layout so the switch takes on the first press.
- CapsLock's normal ALL-CAPS toggle is disabled — it's now your layout
modifier. Use
Shiftfor capitals. (Fully reversible, see below.) - Rare stuck modal. If macOS misses a CapsLock key-up, the letters bound in the modal may briefly stop typing. Fix: tap CapsLock once to reset.
- Some apps bind their own shortcuts; if a chosen letter conflicts inside a
specific app, pick another letter in the
layoutstable.
./uninstall.shThis removes the LaunchAgent and restores CapsLock to normal. It leaves
Hammerspoon and your ~/.hammerspoon/init.lua untouched (delete those manually
if you want a full removal).
MIT — see LICENSE. Do whatever you like; no warranty.
Built with Hammerspoon. PRs and layout tweaks welcome.