SM_TD is a QMK user library that makes Home Row Modifiers (HRMs) and Tap Dance reliable during fast typing. It improves tap vs. hold decisions by analyzing key releases (not just presses).
Typing often involves overlapping keypresses. For example:
↓h ↓i ↑h ↑i
This happens when you type "hi" quickly. But QMK’s default behavior may misinterpret ↓h as a hold, not a tap, because ↓i occurred before ↑h.
This leads to bugs when using keys like LT(1, KC_H) for home row mods — triggering layer_move(1) instead of typing h.
SM_TD solves this by:
- Interpreting keys based on release timing
- Respecting natural typing habits
- Avoiding false holds during fast sequences
This library follows the natural overlap that happens when we type quickly. In the hi example, most people press i before releasing h — i.e., ↓h, ↓i, ↑h, ↑i.
Stock QMK often interprets these in strict press order, which can misclassify a tap-hold key (e.g., LT(1, KC_H)) as a hold, leading to layer_move(1) instead of a tap.
SM_TD respects your habits rather than forcing you to change them. It pays attention to the time between key releases and interprets them accordingly:
↓h,↓i,↑h(tiny pause),↑i→ treat as a combo-like overlap: hold/action onh+ tapi↓h,↓i,↑h(long pause),↑i→ treat as sequential taps: taph+ tapi
- Human-friendly tap+tap vs. hold+tap interpretation for MT and LT
- Per-key behavior tuning (e.g., hold after N taps in a row)
- Immediate Tap Dance-style responses (no extra timeout needed)
- Configurable timeouts per key or globally
- Feature flags per key or globally
- Debugging tools
- Caps Word: integrates with QMK Caps Word
- Combos: partial support for QMK Combos
There are two ways to install SM_TD:
- In
rules.mk, addDEFERRED_EXEC_ENABLE = yes. - In
config.h, add#define MAX_DEFERRED_EXECUTORS 10(or increase if already defined). - Copy
sm_td/sm_td.hinto yourkeymaps/<your_keymap>/folder (next tokeymap.c). - Add
#include "sm_td.h"in yourkeymap.c. - Check
process_smtd(...)first inprocess_record_user(...)like this:
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if (!process_smtd(keycode, record)) {
return false;
}
// your code here
return true;
}-
In
keymap.json, add:{ "modules": ["stasmarkin/sm_td"] }
That’s it — proceed to Configuration.
-
Create an
on_smtd_action()function in yourkeymap.cthat handles extra actions for keycodes. For example, to useKC_A,KC_S,KC_D, andKC_Ffor Home Row Mods:smtd_resolution on_smtd_action(uint16_t keycode, smtd_action action, uint8_t tap_count) { switch (keycode) { SMTD_MT(KC_A, KC_LEFT_GUI) SMTD_MT(KC_S, KC_LEFT_ALT) SMTD_MT(KC_D, KC_LEFT_CTRL) SMTD_MT(KC_F, KC_LSFT) } return SMTD_RESOLUTION_UNHANDLED; }
See the Customization Guide and practical Examples for more patterns.
-
(optional) Add global configuration parameters to your
config.hfile (see timeouts and feature flags). -
(optional) Add per-key configuration (see timeouts and feature flags).
| Macro | Description |
|---|---|
SMTD_MT(KC_A, KC_LEFT_GUI) |
Basic mod-tap: Tap KC_A → single tap, Hold KC_A → KC_LEFT_GUI hold |
SMTD_MT(KC_A, KC_LEFT_GUI, 2) |
Tap count mod-tap: Same as above, but hold after 2 sequential taps results in KC_A hold• ↓KC_A, ↑KC_A, ↓KC_A... → KC_A tap + KC_LEFT_GUI hold• ↓KC_A, ↑KC_A, ↓KC_A, ↑KC_A, ↓KC_A... → 2× KC_A tap + KC_A hold |
SMTD_MT(KC_A, KC_LEFT_GUI, 1, false) |
Caps Word disabled: Basic mod-tap with QMK’s Caps Word feature disabled |
SMTD_MTE(KC_A, KC_LEFT_GUI) |
Eager mod-tap: Holds KC_LEFT_GUI immediately on press• Quick release → KC_LEFT_GUI released + KC_A tapped• Continue holding → KC_LEFT_GUI held, no KC_A tap• Useful for fast mod+mouse clicks |
SMTD_MTE(KC_A, KC_LEFT_GUI, 2) |
Eager with tap count: Eager version of tap count mod-tap |
SMTD_MTE(KC_A, KC_LEFT_GUI, 1, false) |
Eager caps disabled: Eager version with Caps Word disabled |
SMTD_LT(KC_A, 2) |
Layer tap: Momentary layer switching (layer 2), works like SMTD_MT but switches layers instead of modifiers |
SMTD_LT(KC_A, 2, 3) |
Layer tap with count: Hold after 3 sequential taps results in KC_A hold• ↓KC_A, ↑KC_A, ↓KC_A... → KC_A tap + layer 2 activation• ↓KC_A, ↑KC_A, ↓KC_A, ↑KC_A, ↓KC_A, ↑KC_A, ↓KC_A... → 3× KC_A tap + KC_A hold |
SMTD_LT(KC_A, 2, 1, false) |
Layer tap caps disabled: Same as above with Caps Word disabled |
SMTD_MT_ON_MKEY(CKC_A, KC_A, KC_LEFT_GUI) |
Mod-tap with custom keycode: Uses custom keycode CKC_A (do not forget to declare it) in keymap while treating it as KC_A tap and KC_LEFT_GUI hold• Might be used if you need different behavior of KC_A on different layers• Useful for migration from older SM_TD versions or when you need custom keycodes |
SMTD_LT_ON_MKEY(CKC_A, KC_A, 2) |
Layer tap with custom keycode: Uses custom keycode CKC_A (do not forget to declare it) in keymap while treating it as KC_A tap and layer 2 activation• Might be used if you need different behavior of KC_A on different layers• Useful for migration from older SM_TD versions or when you need custom keycodes |
There is a /docs folder with extensive documentation.
Also, you may check my layout for a real-world example of using this library.
Start with GitHub issues or pull requests for questions and ideas.
You can also join the SM_TD Discord channel, or reach me on Reddit (u/stasmarkin) or Discord (stasmarkin).
Also, you may email me or tag/text me on Reddit (u/stasmarkin) or Discord (stasmarkin).
If you find this library helpful, consider supporting the project:
Crypto support:
- USDT on TRON:
TE4QifvjnPSQoT4oJXYnYAnZxBKAvwUFCN - ByBit ID:
230327759
Your support helps me continue developing and maintaining this project. Thank you for using SM_TD!
- 3+ finger roll interpretation
- A collection of useful macros
- Fix: remove 'SMTD_KEYCODES_BEGIN' undeclared error
- Bug fixes
- QMK community module integration
- Fix: smtd_current_keycode is now compatible with AVR (fixes issue #48)
- Feature: add SMTD_MBTE5_ON_MKEY macro
- Feature: split sm_td into .h and .c file
- dynamic timeouts
- better combo support
- other feature requests (see issues)
- stable API
- memory optimizations (on storing active states)
- memory optimizations (on state machine stack size)
- split into header and source files
- teddybear for docs fixes
- MrMustardTBC for docs fixes
- mikenrafter for cool macros
- alextverdyy for qmk module support
- TickKleiner for community module multiple definition error fix
- Azzam S.A
- Thiago Alves
- Julian Hirn
- Beau Haan
- Str8Razor
- PineappleOfD!scord
- Alexander Spitaler
- Josh Stobbs
- Yousef Hadder
- WhoAmiI
- Slava
(please, let me know, if I have forgotten someone)