kanata is a cross-platform keyboard remapper written in Rust, inspired by QMK/kmonad. It runs as a background process reading raw keyboard events and remaps them system-wide, independent of any specific app or OS keyboard layout setting. Config lives in a single .kbd file using an S-expression syntax.
Install
brew install kanata # macOSOn macOS, kanata also needs the Karabiner VirtualHID driver to inject keystrokes, and the binary needs Input Monitoring + Accessibility permissions (System Settings → Privacy & Security) the first time it runs.
Config structure
A config.kbd file has four core sections:
defcfg— global settings.defsrc— the physical key layout being remapped, listed row by row.deflayer <name>— one full layer mapped 1:1 positionally againstdefsrc._means “fall through to the layer below” (base for everything else, defsrc itself for base).defalias— named actions (@name), referenced from a layer as@nameat the matchingdefsrcposition — an alias only takes effect once adeflayerplaces it.
Key actions used below:
tap-hold <tap-ms> <hold-ms> <tap-action> <hold-action>— send<tap-action>on a quick press,<hold-action>if held past<hold-ms>.tap-hold-release— variant oftap-holdthat resolves to tap as soon as another key is pressed and released before the hold timeout, making fast typing feel more responsive. This is what makes home-row mods usable at typing speed.tap-hold-except-keys <tap-ms> <hold-ms> <tap> <hold> (<keys>)— liketap-hold-release, but disables the hold behavior when any of<keys>is pressed first, to avoid misfiring on a specific bigram.layer-toggle <layer>— hold to switch to<layer>, release to fall back to the previous layer.
My configuration
I run a home-row-mods layout with a symbols layer and a numpad-style numbers layer, both reachable from either hand.
(defcfg
process-unmapped-keys yes
)
(defsrc
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
102d 1 2 3 4 5 6 7 8 9 0 - = bspc
tab q w e r t y u i o p [ ] \
caps a s d f g h j k l ; ' ret
lsft grv z x c v b n m , . / rsft
lctl lalt lmet spc rmet ralt rctl
)
(deflayer base
_ brdn brup _ _ _ _ prev pp next mute vold volu
_ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _
@^ @A @S @D @F _ _ @J @K @L @; _ @ret
_ @num @nuz _ _ _ _ _ _ _ _ _ _
_ _ _ @spc _ _ _
)
(defalias
^ (tap-hold 200 200 esc lctl)
A (tap-hold-release 200 250 a lctl)
S (tap-hold-except-keys 200 350 s lalt (e))
D (tap-hold-release 200 250 d lmet)
F (tap-hold-release 200 250 f lsft)
J (tap-hold-release 200 250 j rsft)
K (tap-hold-release 200 250 k rmet)
L (tap-hold-except-keys 200 350 l ralt (i))
; (tap-hold-release 200 250 ; rctl)
num (tap-hold-release 200 250 grv (layer-toggle numbers))
nuz (tap-hold-release 200 250 z (layer-toggle numbers))
spc (tap-hold-release 200 250 spc (layer-toggle symbols))
ret (tap-hold-release 200 250 ret (layer-toggle symbols))
)capstapsesc, holdslctl— the two keys I use most from that corner, reachable without leaving home row.- The mods mirror outward-to-inward on both hands:
a/;→lctl/rctl(pinkies),s/l→lalt/ralt(ring fingers),d/k→lmet/rmet(middle fingers),f/j→lsft/rsft(index fingers). sandlusetap-hold-except-keysinstead oftap-hold-releasebecauseseandliare common enough bigrams that the plain hold-detection would misfire mid-word — excludingeandirespectively fixes that.spc/retboth togglesymbols, andgrv/zboth togglenumbers— each layer is reachable with either thumb/pinky so I don’t need to cross hands to use it.- Function row (
f1-f12) doubles as media keys on tap: brightness, playback, volume.
Symbols layer
(deflayer symbols
_ f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
_ _ _ _ _ _ _ _ _ _ _ _ _ _
_ S-1 S-2 S-3 S-4 S-5 S-6 S-7 S-8 S-9 S-0 S-[ S-] S-\
_ [ ] S-- - = left down up rght S-; S-' _
_ - S-[ S-] S-grv + \ lmet S-\ S-, S-. S-/ _
_ _ _ _ _ _ _
)Held while typing, this trades the function row’s media-key shortcuts back for plain f1-f12, puts the shifted symbols row (!@#$%^&*()) directly under the qwerty row where they’re one row closer to home, and turns h j k l — already the mod-tap keys on base — into vim-style arrows (left down up rght) so navigation never requires the arrow cluster.
Numbers layer
(deflayer numbers
_ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ S-8 7 8 9 - _ _ _
_ _ _ _ _ _ S-= 4 5 6 S-; _ _
_ _ _ _ _ _ _ 0 1 2 3 . _
_ _ _ 0 _ _ _
)Held while typing, this lays out a numpad on the right hand: 7 8 9 on u i o, 4 5 6 on j k l, 1 2 3 on m , ., 0 on both n and spc, plus *, -, +, : alongside for arithmetic without reaching for the number row.
Cheatsheet
kanata --cfg ~/.config/kanata/config.kbd # run with explicit config path
kanata --cfg ~/.config/kanata/config.kbd --debug # verbose logging for troubleshootingRuns at login as a LaunchAgent, via launchctl’s RunAtLoad.