GokuRakuJoudo (Goku) lets you write your Karabiner-Elements keyboard config in a concise EDN format instead of editing Karabiner's 20,000+ line JSON by hand.
This gist walks through setup, explains every major concept, and includes my full 1,500-line karabiner.edn as a working real-world example.
# Install Goku
brew install yqrashawn/goku/goku
# Start the watcher (rebuilds karabiner.json on every save)
brew services start goku
# Or run once manually
gokuRequirements:
- macOS with Karabiner-Elements installed
- A Karabiner profile named "Default" (Goku writes rules into this profile)
Your config lives at ~/.config/karabiner.edn. Every time you save it, gokuw (the watcher) regenerates the complex_modifications section of your karabiner.json.
Rules live inside :main and follow this pattern:
[:from :to :conditions {:options}]Only :from and :to are required:
{:main [{:des "My rules"
:rules [[:caps_lock :escape] ; Caps Lock → Escape
[:a [:1 :2 :3]] ; a → type "123"
[:b "open -a Safari"]]}]} ; b → run shell commandGoku uses a compact prefix notation instead of spelling out modifiers:
| Prefix | Modifier |
|---|---|
!C |
⌘ Command (left) |
!S |
⇧ Shift (left) |
!O |
⌥ Option (left) |
!T |
⌃ Control (left) |
!Q |
⌘ Command (right) |
!R |
⇧ Shift (right) |
!E |
⌥ Option (right) |
!W |
⌃ Control (right) |
!F |
fn |
## |
Optional any modifier |
Combine them freely:
[:!Ca :1] ; Cmd+A → 1
[:!CSa :1] ; Cmd+Shift+A → 1
[:!COSa :1] ; Cmd+Opt+Shift+A → 1
[:##caps_lock :escape] ; Caps Lock → Escape (pass through any held modifiers)This is Goku's killer feature. Press a key simultaneously with another to activate a layer. Tap the key alone and it types normally.
{:simlayers {:s-mode {:key :s}} ; Define: S is a layer trigger
:main [{:des "s-mode navigation"
:rules [:s-mode ; Condition prefix for all rules below
[:h :left_arrow] ; S+H → Left
[:j :down_arrow] ; S+J → Down
[:k :up_arrow] ; S+K → Up
[:l :right_arrow] ; S+L → Right
]}]}How it works:
- Press
S+Htogether (within threshold) → Left Arrow - Press
Salone → typessnormally - The layer key still works for normal typing — you don't lose any keys
Traditional hold-to-activate layers. The key does one thing when tapped, another when held:
{:layers {:tab-mode {:key :tab}} ; Hold Tab for layer, tap for normal Tab
:main [{:des "tab mode"
:rules [:tab-mode
[:s :capture_screenshot]
[:r :record_screen]]}]}You can customize tap behavior with :alone:
{:layers {:escape-mode {:key :escape
:alone [{:key :escape} ; Tap → Escape
{:set ["my-mode" 0]}] ; Also reset a variable
}}}Templates eliminate repetition for shell commands using %s placeholders:
{:templates {:open "open \"%s\""
:launch "open -a \"%s\""
:km "osascript -e 'tell application \"Keyboard Maestro Engine\" to do script \"%s\"'"
:script "/bin/bash ~/.config/scripts/%s.sh"}
:main [{:des "app launchers"
:rules [:launch-mode
[:s [:launch "Slack"]]
[:c [:launch "Chrome"]]
[:f [:open "~/Downloads"]]
[:r [:script "restart_yabai"]]]}]}Name common modifier combos:
{:modifiers {:hyper [:command :shift :control :option]
:co [:command :option]
:cs [:command :shift]}
:main [{:des "hyper shortcuts"
:rules [[:1 {:key :1 :modi :hyper}]]}]} ; 1 → Hyper+1Name common keys or actions to keep rules readable:
{:froms {:delete {:key :delete_or_backspace}
:return {:key :return_or_enter}}
:tos {:spotlight {:key :f13}
:go_back {:key :hyphen :modi :control}
:go_home {:key :up_arrow :modi :command}}
:main [{:des "navigation"
:rules [[:delete :go_home]
[:return :spotlight]]}]}Scope rules to specific apps:
{:applications {:chrome ["com.google.Chrome"]
:code ["com.microsoft.VSCode" "dev.zed.Zed"]
:slack ["com.tinyspeck.slackmacgap"]}
:main [{:des "Chrome shortcuts"
:rules [:chrome ; Condition: Chrome only
[:left_command :left_command
nil {:alone :!Ct}]]} ; Tap Cmd → New Tab
{:des "Code shortcuts"
:rules [:code
[:left_command :left_command
nil {:alone :go_to_file}]]}]} ; Tap Cmd → Go To FileFind bundle IDs: osascript -e 'id of app "Google Chrome"'
Press two keys at the same time for an action:
[:[:j :k] :escape] ; J+K pressed together → EscapeOne action when tapped, another when held:
; Caps Lock: tap → Escape, hold → activate escape-mode layer
[:caps_lock ["escape-mode" 1] nil {:alone :escape
:afterup ["escape-mode" 0]}]
; Left Command: tap → Go To File, hold → Command
[:left_command :left_command nil {:alone :go_to_file}]Set and check variables to create complex behaviors:
; Set a variable
[:w ["my-layer" 1]]
; Use a variable as a condition
[:h :left_arrow ["my-layer" 1]]
; Reset in :afterup
[:w ["my-layer" 1] nil {:afterup ["my-layer" 0]
:alone :w}]Map mouse buttons and create mouse-triggered layers:
{:froms {:right_click {:pkey :button2}}
:main [{:des "mouse layers"
:rules [; Hold Button4 for layer, tap for Space Left
[{:pkey :button4} ["button4-mode" 1]
nil {:alone [:space_left]
:afterup ["button4-mode" 0]}]
; Right Click + Left Click → Shift+Click
:button4-mode
[{:pkey :button1} {:pkey :button1 :modi :left_shift}]]}]}Switch conditions mid-block without nesting:
{:des "Code shortcuts"
:rules [:code ; All rules below are Code-only
[:left_command :left_command nil {:alone :go_to_file}]
[:condi :code :escape-mode] ; Switch to: Code AND escape-mode
[:g :focus_git]
[:f :find_in_project]
[:condi :code :nav-mode] ; Switch to: Code AND nav-mode
[:a :open_prev_editor]
[:t :open_next_editor]]}Trigger different actions based on how many fingers are on the trackpad:
{:main [{:des "1-finger trackpad"
:rules [[:f :button1 ["multitouch_extension_finger_count_total" 1]]
[:d {:pkey :button1 :modi :left_shift}
["multitouch_extension_finger_count_total" 1]]]}]}Requires the Karabiner MultitouchExtension.
See karabiner.edn in this gist for a complete, working config featuring:
- 30+ simlayers covering navigation, editing, app launching, symbols, emojis, window management, and more
- Colemak layout remapping (QWERTY input → Colemak output via Karabiner)
- App-specific rules for Chrome, VS Code/Cursor/Zed, Slack, Terminal, Ableton Live, Final Cut Pro, and more
- Mouse button layers (Button4/Button5 hold for layers, Right Click hold for layer)
- Trackpad finger-count rules (different actions for 1/2/3/4 fingers)
- Window management via Yabai scripts (move/resize/focus windows across displays and spaces)
- Template system for shell commands, AppleScript, Keyboard Maestro macros, and more
- Dual-purpose keys: Caps Lock (tap Escape / hold for layer), Left Command (tap Go To File / hold for Cmd), Tab (tap Tab / hold for layer)
| Layer | Trigger | Purpose |
|---|---|---|
| escape-mode | Hold Escape/Caps | App focus, view switching, space management |
| movement-mode | F + key | Arrow keys (hjkl), line start/end |
| shift-mode | D + key | Shift+Arrow, text selection |
| opt-mode | A + key | Option+Arrow (word jump), AceJump |
| shift-opt-mode | S + key | Shift+Option+Arrow (word select) |
| command-mode | C + key | Cmd+Arrow, go home/end |
| command-shift-mode | V + key | Cmd+Shift+Arrow |
| delete-mode | J + key | Backspace, forward delete, word/line delete |
| nav-mode | K + key | Editor tabs, go back/forward |
| cursor-mode | G + key | Multi-cursor, find match, AceJump |
| spacebar-mode | Space + key | Symbols: [](){}${} => |
| semicolon-mode | ; + key | Special chars: !@#$%^&* |
| snippet-mode | ' + key | Code snippets: console.log(), await, async |
| emoji-mode | Z + key | Paste emojis: 🔥😂🤔👍❤️ |
| launch-mode | U + key / F24 | Launch apps: Slack, Chrome, Zed, Finder |
| period-mode | . + key | Focus or open Chrome tabs (URLs) |
| comma-mode | , + key | Always open new Chrome tab (URLs) |
| go-mode | T + key | IDE go-to: references, symbol, definition |
| peek-mode | R + key | IDE peek: definition, implementations, refs |
| kit-mode | N + key | Open projects in editor |
| media-mode | M + key | Play/pause, next track |
| equal-mode | Hold = | Window management (Yabai) |
| home-mode | Hold Home | Window layout, display management |
| tab-mode | Hold Tab | Screenshots, media, app launch |
| quick-mode | Q + key | Utilities, timestamp, Goku reload |
| close-mode | W + key | Close all/others in IDE |
| finder-mode | E + key | Open folders |
| close-bracket-mode | Hold ] | Window tiling (mwm) |
# 1. Install Goku and Karabiner-Elements
brew install yqrashawn/goku/goku
brew install --cask karabiner-elements
# 2. Copy my config
cp karabiner.edn ~/.config/karabiner.edn
# 3. Customize paths and app references to match your system
# 4. Run Goku
goku
# 5. Start the watcher
brew services start goku- Remove Colemak: Delete the "Colemak Remapping" section at the bottom if you use QWERTY, and change all
:-Xaliases to regular:Xkeys throughout - Templates: Update file paths in
:templatesto match your system - App bundle IDs: Update
:applicationsfor your installed apps - Scripts: The window management templates reference
~/.config/scripts/— replace with your own scripts or remove - Keyboard Maestro:
km()calls require Keyboard Maestro — replace withopen -aor shell commands - URLs in period-mode/comma-mode: Replace with your own frequently visited sites
- Project paths in kit-mode: Replace with your own project directories
- Start with one simlayer. Add
s-modefor navigation, use it for a week, then expand. - Use
goku(one-shot) to test changes before enabling the watcher. - Check logs at
~/Library/Logs/goku.logif rules aren't working. - Use Karabiner EventViewer to find key codes and bundle IDs.
- Simlayer threshold (
:simlayer-threshold) controls how fast you need to chord — lower it if layers activate during normal typing. ##is critical for modifier passthrough. Without it,[:right_command :f16]only fires on bare Right Command.
- GokuRakuJoudo repo — full docs and tutorial
- Karabiner-Elements
- Community configs — real-world examples from other users
- Modifier notation reference