Karabiner-Elements complex_modifications rules
See the codecomplex_modifications for Karabiner-Elements.
https://ke-complex-modifications.pqrs.org/
Rule files can use any of the following formats:
title and rules under public/json.description and manipulators) under
public/json.public/js whose final expression returns one rule
(description and manipulators).Karabiner-Elements 16.2.0 and later can import single-rule JSON and JavaScript
directly. To support Karabiner-Elements 16.1.0 and earlier, the build also
converts these sources to the title and rules ruleset format. The rule's
description is used as the ruleset title.
A single JSON rule is replaced by its ruleset form at the same distribution
path, so there is only one distributed JSON file. JavaScript source remains
available unchanged, and its generated ruleset JSON is written beside it with
the .ruleset.json suffix.
public/json/example.json → dist/json/example.jsonpublic/js/example.js → dist/js/example.jsdist/js/example.ruleset.jsonThe generated dist.json index contains lightweight metadata for display and
search. Full manipulators remain in the distributed JSON files and are not
duplicated in this index.
JSON rulesets bundle multiple rules into a single file so users can pick and enable what they need. For example, the "Emacs key bindings" ruleset includes several rules for different use cases.
| The distributed JSON file | Complex Modifications |
|---|---|
![]() | ![]() |
To bundle them this way, the JSON has the following structure. Each element under rules is a single rule as defined in Complex Modifications.
If you list GitHub usernames in the "maintainers" field, the distribution site will automatically link to those accounts: https://ke-complex-modifications.pqrs.org/
{
"title": "Emacs key bindings (rev XXX)",
"maintainers": ["tekezo"],
"rules": [
{
"description": "Emacs key bindings [control+keys] (rev XXX)",
"manipulators": [
{
"type": "basic",
"from": {},
"to": []
}
]
},
{
"description": "Emacs key bindings [option+keys] (rev XXX)",
"manipulators": [
{
"type": "basic",
"from": {},
"to": []
}
]
}
]
}
The object normally placed inside rules can also be saved directly:
{
"description": "Change caps_lock to escape",
"manipulators": [
{
"type": "basic",
"from": { "key_code": "caps_lock" },
"to": [{ "key_code": "escape" }]
}
]
}
Optional ruleset attribution fields such as maintainers and author can be
included in the same object. They are moved to the ruleset level during the build.
The distributed json/example.json contains the converted ruleset form. The
distribution site imports this file directly and does not provide a separate
compatible JSON action for single JSON rules.
JavaScript is useful when a rule contains repeated definitions. Helper functions can
be defined in the same file. The last expression must return a single rule
(description and manipulators). JSON ruleset objects containing title and
rules are not supported under public/js. For example:
// JavaScript must be written in ECMAScript 5.1.
function main() {
return {
description: 'Change h/j/k/l to arrow keys',
manipulators: [
makeManipulator('h', 'left_arrow'),
makeManipulator('j', 'down_arrow'),
makeManipulator('k', 'up_arrow'),
makeManipulator('l', 'right_arrow'),
],
}
}
function makeManipulator(from, to) {
return {
type: 'basic',
from: { key_code: from },
to: [{ key_code: to }],
}
}
main()
A source named public/js/example.js is distributed as dist/js/example.js.
Its evaluated and normalized JSON ruleset is distributed as
dist/js/example.ruleset.json.
Use js/example.js as its path in public/groups.json.
The build uses the value of the last JavaScript expression; standard output from
the script is ignored.
For JavaScript rules, the distribution site's import menu provides both of the following actions:
Import JavaScript codeImport JSON compatible with Karabiner-Elements 16.1.0 or earlierThe menu can also display the generated JSON with Show compatible JSON and
copy its URL with Copy compatible JSON URL.
Follow the steps below to create a PR and add your settings!
Fork this repository on GitHub.
Clone the repository in Terminal.
git clone --depth 1 https://github.com/{your_account}/KE-complex_modifications.git
cd KE-complex_modifications
git submodule update --init --recursive --depth 1
Create a git branch in Terminal.
git switch -c my-settings
Put a JSON file into public/json or a JavaScript file into public/js. A JSON file can contain either a full ruleset or a single rule. A JavaScript file must return a single rule.
Existing generator files in src/json
are also supported. Their names must end in .json.js; generated JSON is
written to public/json as before.
Add the following entry into public/groups.json.
{
"path": "json/your_awesome_configuration.json", // required
"extra_description_path": "extra_descriptions/your_awesome_configuration.html" // optional
},
You can use the tag <kbd>⇧Shift</kbd> to make a nice ⇧Shift in your html.
Run make command in Terminal to validate your files.
JavaScript files are evaluated for validation. JSON is written under
dist/json, and JavaScript is copied to dist/js. Each JavaScript rule also
produces a sibling *.ruleset.json file. Single JSON rules are converted to
ruleset form at their original path under dist/json. Files in src/json
also update their corresponding generated files under public/json.
make all
If there is a problem, an error message is displayed. Fix your files until no errors are shown.
"../public/json/personal_tekezo.json": `Personal rules (@tekezo) (rev 47)` error: `to` entry error: `key_code` error: unknown key_code: `"space"`
make[1]: *** [all] Error 1
make: *** [all] Error 2
Test your files
Copy the file to test to
~/.config/karabiner/assets/complex_modifications. For a JSON source, copy
the built JSON ruleset. For a JavaScript source, copy the JavaScript file
itself without converting it to JSON.
cp dist/json/your_awesome_configuration.json ~/.config/karabiner/assets/complex_modifications
# For a JavaScript source:
cp dist/js/your_awesome_configuration.js ~/.config/karabiner/assets/complex_modifications
Import rules from Karabiner-Elements Settings > Complex Modifications > Rules > Add rule.
Run local web server by the following command in Terminal.
make preview-server
Then open http://localhost:8000.
Run git commit and git push in Terminal.
git add -A
git commit -m 'your commit message'
git push --set-upstream origin my-settings
Create a PR on GitHub.
Use the description field to explain how each distributed JSON rule works.
If a one-line description is not enough (e.g., for complex rules), you can add supplementary text on the site:
public/extra_descriptions/.public/groups.json.The additional description will then appear on the site as shown in the screenshot.
| JSON only | With extra description |
|---|---|
![]() | ![]() |
{
"path": "json/emacs_key_bindings.json",
"extra_description_path": "extra_descriptions/emacs_key_bindings.json.html"
}
<html> and <body> tags. Write only the HTML for the description section.<img src="multitouch-extension/enable-multitouch-extension.png" class="img-fluid border" />.
Be sure to add the images to the repository.make preview-server command and open http://localhost:8000/ in your browser.
The HTML file will not be loaded unless you specify extra_description_path in public/groups.json, so be sure to update groups.json.To update your previously forked repositories, run the following command in Terminal.
git remote add upstream https://github.com/pqrs-org/KE-complex_modifications.git
# switch to main branch
git switch main
# fetch upstream
git fetch --all --prune --prune-tags
# update local repository
git reset --hard upstream/main
# update submodules
git submodule update --init --recursive
# clean files
git clean -x -d -f .
# update GitHub repository
git push
On macOS, the code in public/js/*.js and src/json/*.js is executed by
Duktape, which is built into the Karabiner-Elements
command line interface (karabiner_cli). The Linux deployment build evaluates
public/js/*.js with Node.js because karabiner_cli is not available there.
JavaScript must remain compatible with the Duktape environment so both builds
produce the same result.
Unlike the latest Node.js, the basic language specification is ES5.1, so the following features cannot be used.
let (const is specially supported)There are many existing codes. Here are some of the more distinctive ones among them.
frontmost_application_if.
368 followers · starred Jul 2017
1,700 followers · starred Apr 2018
477 followers · starred Mar 2019
691 followers · starred Apr 2025
Karabiner-Elements complex_modifications rules
See the codecomplex_modifications for Karabiner-Elements.
https://ke-complex-modifications.pqrs.org/
Rule files can use any of the following formats:
title and rules under public/json.description and manipulators) under
public/json.public/js whose final expression returns one rule
(description and manipulators).Karabiner-Elements 16.2.0 and later can import single-rule JSON and JavaScript
directly. To support Karabiner-Elements 16.1.0 and earlier, the build also
converts these sources to the title and rules ruleset format. The rule's
description is used as the ruleset title.
A single JSON rule is replaced by its ruleset form at the same distribution
path, so there is only one distributed JSON file. JavaScript source remains
available unchanged, and its generated ruleset JSON is written beside it with
the .ruleset.json suffix.
public/json/example.json → dist/json/example.jsonpublic/js/example.js → dist/js/example.jsdist/js/example.ruleset.jsonThe generated dist.json index contains lightweight metadata for display and
search. Full manipulators remain in the distributed JSON files and are not
duplicated in this index.
JSON rulesets bundle multiple rules into a single file so users can pick and enable what they need. For example, the "Emacs key bindings" ruleset includes several rules for different use cases.
| The distributed JSON file | Complex Modifications |
|---|---|
![]() | ![]() |
To bundle them this way, the JSON has the following structure. Each element under rules is a single rule as defined in Complex Modifications.
If you list GitHub usernames in the "maintainers" field, the distribution site will automatically link to those accounts: https://ke-complex-modifications.pqrs.org/
{
"title": "Emacs key bindings (rev XXX)",
"maintainers": ["tekezo"],
"rules": [
{
"description": "Emacs key bindings [control+keys] (rev XXX)",
"manipulators": [
{
"type": "basic",
"from": {},
"to": []
}
]
},
{
"description": "Emacs key bindings [option+keys] (rev XXX)",
"manipulators": [
{
"type": "basic",
"from": {},
"to": []
}
]
}
]
}
The object normally placed inside rules can also be saved directly:
{
"description": "Change caps_lock to escape",
"manipulators": [
{
"type": "basic",
"from": { "key_code": "caps_lock" },
"to": [{ "key_code": "escape" }]
}
]
}
Optional ruleset attribution fields such as maintainers and author can be
included in the same object. They are moved to the ruleset level during the build.
The distributed json/example.json contains the converted ruleset form. The
distribution site imports this file directly and does not provide a separate
compatible JSON action for single JSON rules.
JavaScript is useful when a rule contains repeated definitions. Helper functions can
be defined in the same file. The last expression must return a single rule
(description and manipulators). JSON ruleset objects containing title and
rules are not supported under public/js. For example:
// JavaScript must be written in ECMAScript 5.1.
function main() {
return {
description: 'Change h/j/k/l to arrow keys',
manipulators: [
makeManipulator('h', 'left_arrow'),
makeManipulator('j', 'down_arrow'),
makeManipulator('k', 'up_arrow'),
makeManipulator('l', 'right_arrow'),
],
}
}
function makeManipulator(from, to) {
return {
type: 'basic',
from: { key_code: from },
to: [{ key_code: to }],
}
}
main()
A source named public/js/example.js is distributed as dist/js/example.js.
Its evaluated and normalized JSON ruleset is distributed as
dist/js/example.ruleset.json.
Use js/example.js as its path in public/groups.json.
The build uses the value of the last JavaScript expression; standard output from
the script is ignored.
For JavaScript rules, the distribution site's import menu provides both of the following actions:
Import JavaScript codeImport JSON compatible with Karabiner-Elements 16.1.0 or earlierThe menu can also display the generated JSON with Show compatible JSON and
copy its URL with Copy compatible JSON URL.
Follow the steps below to create a PR and add your settings!
Fork this repository on GitHub.
Clone the repository in Terminal.
git clone --depth 1 https://github.com/{your_account}/KE-complex_modifications.git
cd KE-complex_modifications
git submodule update --init --recursive --depth 1
Create a git branch in Terminal.
git switch -c my-settings
Put a JSON file into public/json or a JavaScript file into public/js. A JSON file can contain either a full ruleset or a single rule. A JavaScript file must return a single rule.
Existing generator files in src/json
are also supported. Their names must end in .json.js; generated JSON is
written to public/json as before.
Add the following entry into public/groups.json.
{
"path": "json/your_awesome_configuration.json", // required
"extra_description_path": "extra_descriptions/your_awesome_configuration.html" // optional
},
You can use the tag <kbd>⇧Shift</kbd> to make a nice ⇧Shift in your html.
Run make command in Terminal to validate your files.
JavaScript files are evaluated for validation. JSON is written under
dist/json, and JavaScript is copied to dist/js. Each JavaScript rule also
produces a sibling *.ruleset.json file. Single JSON rules are converted to
ruleset form at their original path under dist/json. Files in src/json
also update their corresponding generated files under public/json.
make all
If there is a problem, an error message is displayed. Fix your files until no errors are shown.
"../public/json/personal_tekezo.json": `Personal rules (@tekezo) (rev 47)` error: `to` entry error: `key_code` error: unknown key_code: `"space"`
make[1]: *** [all] Error 1
make: *** [all] Error 2
Test your files
Copy the file to test to
~/.config/karabiner/assets/complex_modifications. For a JSON source, copy
the built JSON ruleset. For a JavaScript source, copy the JavaScript file
itself without converting it to JSON.
cp dist/json/your_awesome_configuration.json ~/.config/karabiner/assets/complex_modifications
# For a JavaScript source:
cp dist/js/your_awesome_configuration.js ~/.config/karabiner/assets/complex_modifications
Import rules from Karabiner-Elements Settings > Complex Modifications > Rules > Add rule.
Run local web server by the following command in Terminal.
make preview-server
Then open http://localhost:8000.
Run git commit and git push in Terminal.
git add -A
git commit -m 'your commit message'
git push --set-upstream origin my-settings
Create a PR on GitHub.
Use the description field to explain how each distributed JSON rule works.
If a one-line description is not enough (e.g., for complex rules), you can add supplementary text on the site:
public/extra_descriptions/.public/groups.json.The additional description will then appear on the site as shown in the screenshot.
| JSON only | With extra description |
|---|---|
![]() | ![]() |
{
"path": "json/emacs_key_bindings.json",
"extra_description_path": "extra_descriptions/emacs_key_bindings.json.html"
}
<html> and <body> tags. Write only the HTML for the description section.<img src="multitouch-extension/enable-multitouch-extension.png" class="img-fluid border" />.
Be sure to add the images to the repository.make preview-server command and open http://localhost:8000/ in your browser.
The HTML file will not be loaded unless you specify extra_description_path in public/groups.json, so be sure to update groups.json.To update your previously forked repositories, run the following command in Terminal.
git remote add upstream https://github.com/pqrs-org/KE-complex_modifications.git
# switch to main branch
git switch main
# fetch upstream
git fetch --all --prune --prune-tags
# update local repository
git reset --hard upstream/main
# update submodules
git submodule update --init --recursive
# clean files
git clean -x -d -f .
# update GitHub repository
git push
On macOS, the code in public/js/*.js and src/json/*.js is executed by
Duktape, which is built into the Karabiner-Elements
command line interface (karabiner_cli). The Linux deployment build evaluates
public/js/*.js with Node.js because karabiner_cli is not available there.
JavaScript must remain compatible with the Duktape environment so both builds
produce the same result.
Unlike the latest Node.js, the basic language specification is ES5.1, so the following features cannot be used.
let (const is specially supported)There are many existing codes. Here are some of the more distinctive ones among them.
frontmost_application_if.
368 followers · starred Jul 2017
1,700 followers · starred Apr 2018
477 followers · starred Mar 2019
691 followers · starred Apr 2025