The first time you see a 36-key split keyboard, the reaction is always the same: where did all the keys go? No number row, no dedicated symbols, half the modifiers missing. It looks like a constraint you'd have to suffer through.
It isn't. The trick is that a small board forces you to stop thinking in keys and start thinking in layers. Once that clicks, 36 keys is plenty — and going back to a full-size board feels like reaching across a desk for things that should be under your fingers.
The base layer is the smallest decision
On a normal keyboard the base layer is the whole story. On a small one it's maybe a third of it. The base layer holds letters and not much else, which means every physical key can pull double duty through hold-tap behaviours: tap for a letter, hold for a modifier or a layer.
The two that carry the most weight:
- Home-row mods — the eight keys under your resting fingers become Shift/Ctrl/Alt/GUI when held, letters when tapped.
- Layer-taps on the thumbs — hold to reach a symbol or number layer, tap for space/enter/backspace.
QMK: home-row mods and layer-taps
In QMK this is mostly MT() (mod-tap) and LT() (layer-tap) plus some tuning so fast typing doesn't fire modifiers by accident.
// Home-row mods on the left hand: A=GUI, S=Alt, D=Ctrl, F=Shift
#define HM_A LGUI_T(KC_A)
#define HM_S LALT_T(KC_S)
#define HM_D LCTL_T(KC_D)
#define HM_F LSFT_T(KC_F)
// Thumbs: hold for a layer, tap for a key
#define TH_NAV LT(NAV, KC_SPC)
#define TH_SYM LT(SYM, KC_ENT)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[BASE] = LAYOUT_split_3x5_2(
KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P,
HM_A, HM_S, HM_D, HM_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN,
KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH,
TH_NAV, KC_TAB, KC_BSPC, TH_SYM
),
};The part nobody tells you: the defaults will feel broken for a week. Home-row mods live or die on timing config — TAPPING_TERM, and especially the newer HOLD_ON_OTHER_KEY_PRESS and per-key get_tapping_term() overrides. Get those wrong and "the" turns into a Ctrl-chord. Get them right and you forget the mods are there at all.
Moving to ZMK for wireless
When I cut the cable, QMK's C gave way to ZMK's devicetree. The concepts map over cleanly — the same hold-taps, just declared instead of coded:
/ {
behaviors {
hm: home_row_mod {
compatible = "zmk,behavior-hold-tap";
flavor = "balanced";
tapping-term-ms = <200>;
quick-tap-ms = <150>;
bindings = <&kp>, <&kp>;
};
};
keymap {
compatible = "zmk,keymap";
base {
bindings = <
&kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P
&hm LGUI A &hm LALT S &hm LCTL D &hm LSFT F &kp G /* ... */
>;
};
};
};flavor is the interesting knob. "balanced" waits to see whether you're rolling into another key (a tap) or genuinely holding (a mod), which is exactly the ambiguity home-row mods have to resolve. It's the closest thing ZMK has to QMK's per-key timing surgery, and for most hands it's enough.
What actually made it stick
Two habits, not two features:
- One change at a time. Every layer tweak is a new muscle-memory contract. Change five things and you can't tell which one you hate.
- Log your misfires. For a while I kept a note of every accidental modifier and every reach that hurt. The keymap that emerged after a month of that was better than anything I designed up front.
Thirty-six keys stopped being a constraint and started being the point.