This is my personal ZMK configuration for 34-key Cradio (Hypergolic). It uses contextual hold-tap configuration to make home row modifiers easier to use, along with simple macros to manage the keymap.
ZMK's interrupt based input detection offers a large number of configuration options for managing hold or tap keys. These are my contextual configuration setup for ease of triggering modifiers while avoiding false positives.
Modifiers should not be triggered when a mod-tap key is pressed together with another key on the same hand. However, they should be triggered when held down and another key is tapped with the opposite hand. This is accomplished using balanced
flavor with the following positional hold-tap behavior for left and right mod-taps:
#define TAPPING_TERM 240
#define SHIFT_TAP_MS 170
#define QUICK_TAP_MS 140
lmt: left_mod_tap {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "preferred";
tapping-term-ms = <TAPPING_TERM>;
quick-tap-ms = <QUICK_TAP_MS>;
bindings = <&kp>, <&kp>;
require-prior-idle-ms = <QUICK_TAP_MS>;
hold-trigger-key-positions = <
5 6 7 8 9
13 15 16 17 18 19
25 26 27 28 29
30 31 32 33
>;
};
rmt: right_mod_tap {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "preferred";
tapping-term-ms = <TAPPING_TERM>;
quick-tap-ms = <QUICK_TAP_MS>;
bindings = <&kp>, <&kp>;
require-prior-idle-ms = <QUICK_TAP_MS>;
hold-trigger-key-positions = <
0 1 2 3 4
10 11 12 13 14 16
17 21 22 23 24
30 31 32 33
>;
};
Hold trigger keys are setup with positions on the opposite hand of each side for ease of activation. The require-prior-idle-ms
feature is used to prevent unintended modifier activation during regular typing.
The home row hold-tap Shift key is setup with a shorter tapping term, using balanced
flavor and without require-prior-idle-ms
for quicker capitalization while typing. They are defined with the following bindings:
lst: left_shift_tap {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <SHIFT_TAP_MS>;
quick-tap-ms = <QUICK_TAP_MS>;
bindings = <&kp>, <&kp>;
hold-trigger-key-positions = <
5 6 7 8 9
15 16 17 18 19
25 26 27 28 29
30 31 32 33
>;
};
rst: right_shift_tap {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
flavor = "balanced";
tapping-term-ms = <SHIFT_TAP_MS>;
quick-tap-ms = <QUICK_TAP_MS>;
bindings = <&kp>, <&kp>;
hold-trigger-key-positions = <
0 1 2 3 4
10 11 12 13 14
20 21 22 23 24
30 31 32 33
>;
};
Preprocessor macros can minimize code duplication, making it easier to update and modify behavior nodes over time.
Repetitive hold-tap behavior nodes can be simplified to single-line macros that allows customization of home row mod-tap actions with the following definition:
#define HOLD_TAP(_name_, \
_flavor_, \
_tapping_term_, \
_require_prior_idle_, \
_key_positions_) \
_name_: hold_tap_##_name_ { \
compatible = "zmk,behavior-hold-tap"; \
#binding-cells = <2>; \
flavor = #_flavor_; \
quick-tap-ms = <QUICK_TAP_MS>; \
tapping-term-ms = <_tapping_term_>; \
require-prior-idle-ms = <_require_prior_idle_>; \
hold-trigger-key-positions = <_key_positions_>; \
bindings = <&kp>, <&kp>; \
};
#define L_SHIFT 13
#define R_SHIFT 16
#define L_KEYS 0 1 2 3 4 10 11 12 13 14 20 21 22 23 24 30 31 32 33
#define R_KEYS 5 6 7 8 9 15 16 17 18 19 25 26 27 28 29 30 31 32 33
/ {
behaviors {
// Positional hold-tap for non-Shift modifiers
HOLD_TAP(lmt, tap-preferred, TAPPING_TERM, QUICK_TAP_MS, L_SHIFT R_KEYS)
HOLD_TAP(rmt, tap-preferred, TAPPING_TERM, QUICK_TAP_MS, R_SHIFT L_KEYS)
// Positional hold-tap for Shift
HOLD_TAP(lst, balanced, SHIFT_TAP_MS, 0, R_KEYS)
HOLD_TAP(rst, balanced, SHIFT_TAP_MS, 0, L_KEYS)
};
};
Both combos and macros are also simplified into one-liners using the following preprocessors:
#define COMBO(_name_, _bindings_, _key_positions_) \
combo_##_name_ { \
timeout-ms = <30>; \
require-prior-idle-ms = <COMBO_IDLE_MS>; \
bindings = <_bindings_>; \
key-positions = <_key_positions_>; \
layers = <0 1>; \
};
/ {
combos {
compatible = "zmk,combos";
COMBO(caps_w, &caps_word, 13 16)
};
};
#define MACRO(_name_, _bindings_) \
_name_: macro_##_name_ { \
compatible = "zmk,behavior-macro"; \
#binding-cells = <0>; \
tap-ms = <1>; \
wait-ms = <1>; \
bindings = <_bindings_>; \
};
/ {
macros {
MACRO(dir_up, &kp DOT &kp DOT &kp FSLH)
MACRO(bt_0, &out OUT_BLE &bt BT_SEL 0 &bt BT_DISC 1)
};
};
- nice!nano v2 Pinout
- Customizing ZMK
- Hypergolic PCBs
- Sockets
- Keymapviz
- Keymap Drawer
- Machined header sockets
- Mill-Max 315-43-164-41-001000 sockets
- Mill-Max connector pins
- 301230 Li-po batteries
- MSK-12C02 SMD Switch
- Silicone bumper feet
- Kailh gchoc v1 switches