kntnk
What is kntnk?
kntnk is a tool for writing configurations for the Mac keyboard customization tool Karabiner-Elements more easily.
Karabiner-Elements lets you freely change key assignments. However, its configuration file (karabiner.json) uses JSON with deeply nested {} and [], making even small changes cumbersome.
With kntnk, you can express the same configuration concisely and clearly.
Safari
caps_lock : escape
f3 : ⌘f
This means, "Only while using Safari, assign Caps Lock to Escape and F3 to ⌘F." kntnk automatically generates karabiner.json from this description.
What You Can Do with kntnk
- Configure keys with simple syntax — Create configuration files that are easy for anyone to read and write using a concise dedicated notation (DSL).
- Switch keys by application — Configure keys according to the foreground application, such as using F3 for search in Safari and assigning it differently in VS Code.
- Specify detailed conditions with languages and variables — Define conditions such as enabling a binding only when the Japanese input language is active.
- Update while protecting existing configuration — kntnk automatically creates a backup before writing and preserves non-target profiles and the
global and devices settings. However, all existing rules in an output target profile are replaced by the generated result.
- Get real-time editing assistance in VS Code — The VS Code extension provides syntax highlighting, completion, and error reporting so that mistakes can be detected before saving. kntnk can be used with other text editors, but using it together with the VS Code extension is strongly recommended for accurate key-code entry and early error detection.
Installation
Requirements
- macOS (only Macs with Apple Silicon / M-series chips are supported)
- Karabiner-Elements must be installed
- VS Code (or a derivative editor such as Cursor, Antigravity IDE, or Windsurf IDE) — It is not technically required, but writing configurations without completion and error detection is extremely difficult, so it is effectively necessary
Intel Macs (x86_64), Windows, and Linux are not supported.
1. Install the kntnk CLI
Download the executable from the GitHub repository and place it in ~/.local/bin/.
# Create ~/.local/bin if it does not exist
mkdir -p ~/.local/bin
# Download the kntnk binary from the repository and place it here
# (Download it from the latest releases page)
cp kntnk ~/.local/bin/kntnk
chmod +x ~/.local/bin/kntnk
Run the following in a terminal to verify the installation.
kntnk --version
The installation succeeded if a version number is displayed.
--version does not read or write .kntnk files or karabiner.json. Run kntnk --help for usage information.
2. Install the VS Code Extension (Recommended)
Search for and install "kntnk DSL Support" in the VS Code Marketplace or Open VSX Registry, or download the VSIX file from the repository and install it manually.
For manual installation:
- Download the
kntnk-*.vsix file from the repository
- Open VS Code and select "Extensions: Install from VSIX..." from the Command Palette (
⌘⇧P)
- Select the downloaded
.vsix file
Usage
Basic Workflow
- Create a configuration folder and a
.kntnk file
- Write key bindings
- Move to that folder in a terminal and run
kntnk
Step 1: Create a Configuration File
Create a folder anywhere and create a file with the .kntnk extension.
mkdir ~/my-keybindings
cd ~/my-keybindings
touch my-keys.kntnk
Step 2: Write Key Bindings
Open the file in a text editor or VS Code and use the following format.
condition
input_key : output_key
Example:
// Enabled in all applications
always
caps_lock : escape
// Enabled only while using Safari
Safari
f3 : ⌘f
f4 : ⌘l
// Enabled only while using VS Code
VSCode
f5 : ⌘⇧f
Everything from // to the end of the line is ignored as a comment.
Writing Conditions
| Syntax |
Meaning |
always |
Always enabled |
Safari |
Specify an application by an alias registered with kntnk |
"com.apple.Safari" |
Specify an application directly by Bundle Identifier |
not Safari |
Enabled outside Safari |
language("ja") |
Enabled when the input language is Japanese |
Writing Keys
Write modifier keys as symbols.
| Symbol |
Key |
⌘ |
Command |
⇧ |
Shift |
⌥ |
Option |
⌃ |
Control |
FN |
fn |
CL |
Caps Lock |
Use the official Karabiner-Elements names for key codes (for example, spacebar, return_or_enter, and left_arrow).
When the VS Code extension is installed, completion candidates are displayed, saving you the effort of looking up exact names.
Modifier-key symbols can also be inserted through completion. For example, start typing command, shift, option, control, fn, or caps_lock in a .kntnk file, then select the candidate to insert ⌘, ⇧, ⌥, ⌃, FN, or CL.
Step 3: Generate karabiner.json
In a terminal, move to the folder containing the .kntnk file and run kntnk.
cd ~/my-keybindings
kntnk
This automatically updates ~/.config/karabiner/karabiner.json. A backup (karabiner_backup.json) is created before the update.
When No Profile Is Specified
If [profile] "..." is not specified in a .kntnk file, kntnk automatically uses kntnk as the Karabiner-Elements profile name.
After running the command, open Karabiner-Elements and select the kntnk profile under Profiles. If the kntnk profile is not selected, the generated key bindings are not applied to the profile currently in use.
To output to a different profile, add the following, for example at the beginning of a .kntnk file.
[profile] "Profile Name"
For questions about configuring Karabiner-Elements or Hammerspoon, refer to their official documentation.
Editing with the VS Code Extension
When the VS Code extension is installed, the following features are available when you open a .kntnk file.
- Syntax highlighting — Conditions, key codes, modifier keys, and other elements are color-coded
- Completion — Key-code, application-name, and modifier-symbol candidates are displayed automatically. Selecting
command inserts ⌘, selecting shift inserts ⇧, and so on
- Error detection — Mistakes appear in the Problems panel before saving
- Formatting — Files are formatted automatically when saved
You can also generate karabiner.json by running
kntnk: Generate Karabiner JSON from the VS Code Command Palette (⌘⇧P).
More Useful Features
Various Actions
In addition to sending key codes, key bindings can perform various actions.
Open a File or URL: open()
open("path or URL") opens the specified file or URL using the standard macOS mechanism.
// Open a file
always
f1 : open("~/Documents/memo.txt")
// Open a URL
always
f2 : open("https://example.com")
Variables can be expanded inside string literals using the {variable_name} form.
[set_variable] docs_dir, "~/very_long_name_directory/"
always
f1 : open("{docs_dir}notes.txt")
To include a double quote in a path or URL, write `". To include a backtick itself, write ``. Escape braces that must not be expanded as variables with `{ and `}.
// Example containing double quotes in the path (actual path: ~/Documents/file"note".txt)
always
f3 : open("~/Documents/file`"note`".txt")
If open fails, an error message is output and the Sosumi error sound plays.
Run a Shell Command: sh()
sh("command string") runs the specified shell command as written.
// Run a shell command
always
f3 : sh("pmset displaysleepnow")
// inside a string literal is not treated as a comment. Commands containing URLs can therefore be written as-is.
always
f4 : sh("curl https://example.com/api // this is not a comment")
The escaping rules are the same as for open() (`" for a double quote and `` for a backtick).
Run an Applet: applet()
An applet is a small application created as a macOS .app bundle. It can be created with Script Editor (AppleScript), Automator, or a similar tool.
To use applet(), first configure the applet search directory with [set_applets_dir].
// Configure the applet search directory
[set_applets_dir] "~/Documents/MyApplets"
always
f1 : applet(check_mouse_battery)
f2 : applet(OpenNotes)
When you write applet(OpenNotes), kntnk searches the configured directory for OpenNotes.app and runs it with the open command (the .app extension is appended automatically).
applet() refers only to directories registered before its position in the source. Using applet() before [set_applets_dir] is an error. If the applet is not found, an error message is output and the error sound plays.
Call a Hammerspoon Function: hs()
Hammerspoon is a powerful macOS automation tool controlled with Lua scripts. hs(function_name) calls a Lua function in the running Hammerspoon instance.
// Call Hammerspoon functions
always
f4 : hs(myFunction)
f5 : hs(toggleDarkMode)
Writing hs(myFunction) executes /usr/local/bin/hs -c 'myFunction()'.
Prerequisite: Add loading of the hs.ipc module and definitions of the functions to call to ~/.hammerspoon/init.lua. kntnk cannot call Hammerspoon functions without this setup.
-- ~/.hammerspoon/init.lua
require("hs.ipc") -- Enable the CLI tool (hs command)
function myFunction()
hs.alert.show("Hello from Hammerspoon!")
end
function toggleDarkMode()
hs.osascript.applescript([[
tell application "System Events"
tell appearance preferences
set dark mode to not dark mode
end tell
end tell
]])
end
The hs command must be installed once. Run the following in the Hammerspoon console (menu bar icon → Open Console).
hs.ipc.cliInstall()
hs() does not work if Hammerspoon is not running.
Do Nothing: nop
nop is a special keyword that explicitly performs no action for an input key. Use it, for example, to disable a key.
always
f1 : nop
Write nop on the output side (to the right of :) as the only action. It cannot be combined with another key or action or used after @,, which denotes actions performed when the key is released.
// Invalid examples
always
f1 : a, nop
f2 : nop, b
f3 : @, nop
Run When the Key Is Released: @,
When @, appears among the actions, all subsequent actions execute when the key is released.
// Run when the key is released
always
f13 : set(fn_mode, "1"), @, set(fn_mode, "0")
When F13 is pressed → set fn_mode to "1"
When F13 is released → set fn_mode to "0"
For a more advanced example using this mechanism, see "Use Any Key Like a Modifier Key" below.
Organize Configuration Across Multiple Files
You can place multiple .kntnk files in a folder and organize them into subfolders.
my-keybindings/
global.kntnk # Configuration that is always enabled
browsers.kntnk # Browser configuration
tools/
vscode.kntnk # VS Code configuration
Files placed in a folder named __kntnk_ignore are not loaded. This is useful for moving aside configuration that you want to disable temporarily.
File Combination Order
Multiple .kntnk files are combined in a deterministic order according to the following rules before they are processed.
Basic rule: Traverse "depth-first in folder-name order," and process the .kntnk files in each folder in filename order. When processing a folder, subfolders come first and files directly under that folder come afterward.
Consider the following structure.
my-keybindings/
browsers/
chrome.kntnk
safari.kntnk
tools/
vscode.kntnk
global.kntnk
shortcuts.kntnk
The files are loaded in this order:
1. browsers/chrome.kntnk ← Process the browsers/ subfolder first (in filename order)
2. browsers/safari.kntnk
3. tools/vscode.kntnk ← Process the next subfolder, tools/
4. global.kntnk ← Finally process files directly under the root in filename order
5. shortcuts.kntnk
The same rules apply recursively to deeper nesting.
my-keybindings/
a/
deep/
z.kntnk ← 1st (process a/'s deep/ subfolder first)
a.kntnk ← 2nd (process files directly under a/ after deep/)
b/
b.kntnk ← 3rd
root.kntnk ← 4th (files directly under the root come last)
Situations where this order matters:
Directive Scope
[set_variable] and [set_applets_dir] carry across files. Variables and configured directories defined in one file can be referenced by files loaded later. Conversely, configuration from a file that has not yet been loaded cannot be referenced. When splitting configuration across files, choose folder and file names so that dependent directives are loaded first.
Forward references are not allowed within the same file either. When using a variable in the right-hand side of [set_variable], [set_applets_dir], a condition, or an action, always write [set_variable] first. When switching profiles, variables and applet search directories are retained per profile, and returning to the same profile restores its previous state.
Behavioral Differences Caused by Subset Conditions
When bindings target the same key and their conditions conflict, the one written first takes priority.
// Example 1
Safari
f1 : sh("echo Running in Safari.")
always
f1 : nop
// Example 2
always
f1 : nop
Safari
f1 : sh("echo Running in Safari.")
When F1 is pressed in Safari, Example 1 displays "Running in Safari.", while Example 2 displays nothing.
Use Any Key Like a Modifier Key
Karabiner-Elements has a feature for detecting simultaneous key presses, but kntnk does not support it. Instead, you can treat any key like a modifier by combining variables and if() conditions.
Basic Pattern
The following example creates key bindings that are enabled only while F13 is held.
Step 1: In an always block, configure the variable to become "1" when F13 is pressed and return to "0" when it is released.
always
f13 : set(fn_mode, "1"), @, set(fn_mode, "0")
Step 2: In an if(fn_mode, "1") condition block, write the key bindings that should be enabled only while F13 is held.
if(fn_mode, "1")
h : left_arrow
j : down_arrow
k : up_arrow
l : right_arrow
semicolon : delete_or_backspace
With this configuration, H/J/K/L function as arrow keys only while F13 is held. Releasing F13 restores normal key input.
Layer Multiple "Modifier Keys"
You can define multiple virtual modifier keys in the same way and create conditions that combine them with and_if().
// Use F13 as fn_mode and F14 as extend_mode
always
f13 : set(fn_mode, "1"), @, set(fn_mode, "0")
f14 : set(extend_mode, "1"), @, set(extend_mode, "0")
// fn_mode alone
if(fn_mode, "1")
h : left_arrow
l : right_arrow
// Combination of fn_mode + extend_mode
if(fn_mode, "1") and_if(extend_mode, "1")
h : home
l : end
Combine with Applications
Combining an application condition with and_if() lets you create bindings that work only when a virtual modifier is active in a specific application.
always
f13 : set(fn_mode, "1"), @, set(fn_mode, "0")
VSCode and_if(fn_mode, "1")
h : left_arrow
j : down_arrow
k : up_arrow
l : right_arrow
Notes
It is syntactically possible to use actual modifier keys (command, shift, control, option, and fn) themselves as triggers that switch variables with set(). However, because kntnk does not automatically add optional: ["any"], simultaneous presses with other keys may not work correctly. Using normal keys such as f13 through f20 as virtual modifiers is recommended.
Common Mistakes
Incorrect key-code names
The Space key is spacebar, not space; Enter is return_or_enter, not enter; and Left Arrow is left_arrow, not left. You can confirm the correct names using completion and error reporting in the VS Code extension.
Wrong modifier-key symbol
⌃ (U+2303) and ^ (caret) are different characters. Always use ⌃ for the Control key. Use the extension's completion when entering it in VS Code.
Conditions are not applied
Make sure Karabiner-Elements is running. After generation, also verify that the profile is selected in the Karabiner-Elements settings.
applet() does not work
Verify that [set_applets_dir] is declared before applet(). If [set_applets_dir] is in another file, file loading order may cause it to be loaded after applet().
hs() does not work
Make sure Hammerspoon is running. Also verify that init.lua contains require("hs.ipc") and that the hs command has been installed with hs.ipc.cliInstall().