T
Typeractive.xyzโ€ข3w ago
John

How can i make reusable combined modifiers?

I would like to make my keymap node a bit neater. How can I combine modifiers and reuse it for any keypress I want? I am not too much of a fan of the nested keycodes as it can get quite long: LC(LA(LG(LS(KP))))
7 Replies
TyperAI
TyperAIโ€ข3w ago
Great question! ๐Ÿ‘ What youโ€™re looking for is macros/aliases in ZMK so you donโ€™t have to keep stacking LC(LA(LG(LS(...)))) everywhere.
In ZMK, you can define your own custom behaviors and then reuse them anywhere in the keymap. This makes things neater and shorter.
Here are a couple of approaches: --- 1. Make a combo modifier macro in your keymap.keymap (or .overlay file) You can define a behavior alias that "wraps" the modifiers you want:
/ {
behaviors {
mymods: mod_combo {
compatible = "zmk,behavior-mod-morph";
label = "MOD_COMBO";
#binding-cells = <1>;
bindings = <&kp>, <&kp>;
mods = <(MOD_LCTL|MOD_LALT|MOD_LGUI|MOD_LSFT)>;
};
};
};
/ {
behaviors {
mymods: mod_combo {
compatible = "zmk,behavior-mod-morph";
label = "MOD_COMBO";
#binding-cells = <1>;
bindings = <&kp>, <&kp>;
mods = <(MOD_LCTL|MOD_LALT|MOD_LGUI|MOD_LSFT)>;
};
};
};
Then in your keymap section, you can just do:
bindings = <
&mymods KP_A
&mymods KP_B
>;
bindings = <
&mymods KP_A
&mymods KP_B
>;
This way, the giant LC(LA(LG(LS(...)))) gets replaced with a single reusable behavior. --- 2. Use ZMK macros if you need more control You can also use &macro with modifiers + key:
&macro PRESS LS(LC(LA(LG(&kp KP_A))))
&macro PRESS LS(LC(LA(LG(&kp KP_A))))
โ€ฆbut this still looks a bit long. Using the custom behavior alias method is cleaner if youโ€™ll be reusing it across multiple keys. --- 3. Keep things readable If you just want compact aliases, another trick is to define in your keymap file something like:
#define SUPER_HYPER(key) LC(LA(LG(LS(key))))
#define SUPER_HYPER(key) LC(LA(LG(LS(key))))
Then wherever you want that combo:
bindings = <
SUPER_HYPER(KP_A)
SUPER_HYPER(KP_B)
>;
bindings = <
SUPER_HYPER(KP_A)
SUPER_HYPER(KP_B)
>;
--- ๐Ÿ‘‰ If you want to dive further, check out the ZMK Keymaps docs.
Would you like me to draft a drop-in config snippet for your keymap.keymap that you can copy/paste and just call &allmods KEY for any key you want?
John
JohnOPโ€ข3w ago
what are binding cells? Are they related to bindings? Why does your first example only have 1 binding cell but two binding things? (kp kp) what are binding cells? Are they related to bindings? Why does your first example only have 1 binding cell but two binding things? (kp kp) what are binding cells? Are they related to bindings? Why does your first example only have 1 binding cell but two binding things? (kp kp) what are binding cells? Are they related to bindings? Why does your first example only have 1 binding cell but two binding things? (kp kp)
TyperAI
TyperAIโ€ข3w ago
Really good observation โ€” this part of ZMK can look confusing the first time you see it! Letโ€™s break it down: --- bindings vs #binding-cells - bindings โ†’ the actual key behaviors that a behavior node (like a mod-morph, macro, tap-dance, etc.) will call. These are things like &kp, &mo, &macro etc. - #binding-cells โ†’ tells ZMK how many extra arguments youโ€™re expected to provide when you use that behavior. Example:
- &kp has #binding-cells = <1> because it needs 1 argument (the keycode). - &kp C โ†’ one binding-cell used.
- &mo has #binding-cells = <1> because it needs 1 argument (the layer number).
- &mo 1 โ†’ one binding-cell used.
If a behavior doesnโ€™t require arguments, it usually has #binding-cells = <0>. --- Why did I show bindings = <&kp>, <&kp>; That was using the mod-morph behavior as an example, which defines two branches (like "apply mods" vs "donโ€™t apply mods"). Each &kp inside bindings is a "template" where your provided binding-cells will be slotted in later. So then, when you call it like:
&mymods KP_A
&mymods KP_A
That KP_A is just the 1 binding-cell passed in, and both <&kp> instances can use that same argument.
So: - #binding-cells = <1> โ†’ you give it one argument (like KP_A) - bindings = <&kp>, <&kp>; โ†’ there are two possible branches, but each gets fed that same one argument. --- โœ… In short:
- #binding-cells = "how many arguments do I need when someone calls me?"
- bindings = "what behaviors should I run with that argument plugged in?"
--- Would you like me to give you a side-by-side tiny example (like a behavior with #binding-cells = <0> vs one with <1>) so you can immediately see how the arguments map into the bindings?
John
JohnOPโ€ข3w ago
how can i define key positions? I dont want to have to type out each number how can i define key positions? I dont want to have to type out each number each Ime
TyperAI
TyperAIโ€ข3w ago
Ah, I see what you mean โ€” when youโ€™re filling out the keymap section, you donโ€™t want to manually type raw positions like 0 1 2 ... or have to count key slots every time. ๐Ÿ‘
In ZMKโ€™s .keymap (or .overlay) files, there are a couple of ways to make this cleaner: --- 1. Use keymap { ... }; layout labels The keymap node is actually indexed by position, but most ZMK boards (Corne, Lily58, etc.) come with a keymap layout macro that already defines human-friendly labels for positions.
For example in a Corne config, instead of:
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp Q &kp W &kp E &kp R &kp T
&kp Y &kp U &kp I &kp O &kp P
...
>;
};
};
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp Q &kp W &kp E &kp R &kp T
&kp Y &kp U &kp I &kp O &kp P
...
>;
};
};
You donโ€™t type numbers at all โ€” you just drop keys into the grid order the board definition already expects. ZMK assigns position indices automatically. --- 2. Use #define aliases for positions If you want reusable named positions, you can make simple macros:
#define ESC &kp ESC
#define TAB &kp TAB
#define CTL_A LC(&kp A)
#define ESC &kp ESC
#define TAB &kp TAB
#define CTL_A LC(&kp A)
Now your layout is clean like:
bindings = <
ESC &kp Q &kp W &kp E &kp R
TAB &kp A &kp S &kp D CTL_A
>;
bindings = <
ESC &kp Q &kp W &kp E &kp R
TAB &kp A &kp S &kp D CTL_A
>;
This way you donโ€™t touch the numbers at all. --- 3. For complex composability โ€” use keymap.dtsi includes You can put all your position -> label mappings into a separate file and include it, so your main keymap only shows roles/functions. --- ๐Ÿ‘‰ Bottom line: You donโ€™t usually define positions yourself. The PCB shield defines the position order once, and you just fill the bindings matrix row by row. If you want friendlier names, use #defines to alias any frequently used combos. --- Would you like me to show you the Corne keymap template we ship, so you can see exactly how positions are already flattened row-by-row (no typing numbers needed)?
John
JohnOPโ€ข3w ago
how can i use includes? this is from urob i want to use it /* 42 KEY MATRIX / LAYOUT MAPPING โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ 0 1 2 3 4 5 โ”‚ 6 7 8 9 10 11 โ”‚ โ”‚ LT5 LT4 LT3 LT2 LT1 LT0 โ”‚ RT0 RT1 RT2 RT3 RT4 RT5 โ”‚ โ”‚ 12 13 14 15 16 17 โ”‚ 18 19 20 21 22 23 โ”‚ โ”‚ LM5 LM4 LM3 LM2 LM1 LM0 โ”‚ RM0 RM1 RM2 RM3 RM4 RM5 โ”‚ โ”‚ 24 25 26 27 28 29 โ”‚ 30 31 32 33 34 35 โ”‚ โ”‚ LB5 LB4 LB3 LB2 LB1 LB0 โ”‚ RB0 RB1 RB2 RB3 RB4 RB5 โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ 36 37 38 โ”‚ 39 40 41 โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ LH2 LH1 LH0 โ”‚ RH0 RH1 RH2 โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ / #pragma once #define LT0 5 // left-top row #define LT1 4 #define LT2 3 #define LT3 2 #define LT4 1 #define LT5 0 #define RT0 6 // right-top row #define RT1 7 #define RT2 8 #define RT3 9 #define RT4 10 #define RT5 11 #define LM0 17 // left-middle row #define LM1 16 #define LM2 15 #define LM3 14 #define LM4 13 #define LM5 12 #define RM0 18 // right-middle row #define RM1 19 #define RM2 20 #define RM3 21 #define RM4 22 #define RM5 23 #define LB0 29 // left-bottom row #define LB1 28 #define LB2 27 #define LB3 26 #define LB4 25 #define LB5 24 #define RB0 30 // right-bottom row #define RB1 31 #define RB2 32 #define RB3 33 #define RB4 34 #define RB5 35 #define LH0 38 // left thumb keys #define LH1 37 #define LH2 36 #define RH0 39 // right thumb keys #define RH1 40 #define RH2 41 how can i use includes? this is from urob i want to use it / 42 KEY MATRIX / LAYOUT MAPPING โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ 0 1 2 3 4 5 โ”‚ 6 7 8 9 10 11 โ”‚ โ”‚ LT5 LT4 LT3 LT2 LT1 LT0 โ”‚ RT0 RT1 RT2 RT3 RT4 RT5 โ”‚ โ”‚ 12 13 14 15 16 17 โ”‚ 18 19 20 21 22 23 โ”‚ โ”‚ LM5 LM4 LM3 LM2 LM1 LM0 โ”‚ RM0 RM1 RM2 RM3 RM4 RM5 โ”‚ โ”‚ 24 25 26 27 28 29 โ”‚ 30 31 32 33 34 35 โ”‚ โ”‚ LB5 LB4 LB3 LB2 LB1 LB0 โ”‚ RB0 RB1 RB2 RB3 RB4 RB5 โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ 36 37 38 โ”‚ 39 40 41 โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ LH2 LH1 LH0 โ”‚ RH0 RH1 RH2 โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ */ #pragma once #define LT0 5 // left-top row #define LT1 4 #define LT2 3 #define LT3 2 #define LT4 1 #define LT5 0 #define RT0 6 // right-top row #define RT1 7 #define RT2 8 #define RT3 9 #define RT4 10 #define RT5 11 #define LM0 17 // left-middle row #define LM1 16 #define LM2 15 #define LM3 14 #define LM4 13 #define LM5 12 #define RM0 18 // right-middle row #define RM1 19 #define RM2 20 #define RM3 21 #define RM4 22 #define RM5 23 #define LB0 29 // left-bottom row #define LB1 28 #define LB2 27 #define LB3 26 #define LB4 25 #define LB5 24 #define RB0 30 // right-bottom row #define RB1 31 #define RB2 32 #define RB3 33 #define RB4 34 #define RB5 35 #define LH0 38 // left thumb keys #define LH1 37 #define LH2 36 #define RH0 39 // right thumb keys #define RH1 40 #define RH2 41
TyperAI
TyperAIโ€ข3w ago
Perfect โ€” thatโ€™s exactly the trick people use to avoid typing raw matrix numbers!
Hereโ€™s how to use that include pattern in your ZMK config: --- 1. Put the file somewhere ZMK can see it Usually people make a config folder in their keymap repo and drop this in as positions.dtsi (or .h if you like C preprocessor style).
Example:
config/
โ”œโ”€ keymap.keymap
โ”œโ”€ positions.dtsi โ† your urob mapping file
config/
โ”œโ”€ keymap.keymap
โ”œโ”€ positions.dtsi โ† your urob mapping file
--- 2. Include it in keymap.keymap At the top of your keymap.keymap (or overlay file):
#include "positions.dtsi"
#include "positions.dtsi"
Now all your #define LT0 ... etc. become available. --- 3. Use the names instead of numbers So instead of writing positional index numbers (0, 1, 2โ€ฆ), you can use those labels in your bindings:
/ {
keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
[LT0] &kp Q [LT1] &kp W [LT2] &kp E [LT3] &kp R [LT4] &kp T [LT5] &kp Y
[LM0] &kp A [LM1] &kp S [LM2] &kp D ...
[LH0] &kp SPACE [LH1] &kp ENTER [LH2] &kp ESC
>;
};
};
};
/ {
keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
[LT0] &kp Q [LT1] &kp W [LT2] &kp E [LT3] &kp R [LT4] &kp T [LT5] &kp Y
[LM0] &kp A [LM1] &kp S [LM2] &kp D ...
[LH0] &kp SPACE [LH1] &kp ENTER [LH2] &kp ESC
>;
};
};
};
Here, the square brackets ([...]) tell ZMK which position index youโ€™re filling, and your include file gives you human-friendly aliases like LT0, LM0, etc. instead of 3, 14, 37โ€ฆ. --- 4. Why this works - ZMK internally uses 0โ€“41 position IDs for a 42-key board.
- Your file maps each one to something like LT0, LH1.
- When you #include, the preprocessor replaces them โ†’ more readable keymap. --- โœ… That way you get a visual mental model of rows/columns in your keymap, and you never have to count positions by hand again.
--- Do you want me to show you a minimal Corne keymap example using these includes, so you see line-by-line what it looks like in practice?

Did you find this page helpful?