
Zmk
- 2 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Reference ZMK firmware for keyboard builds covering keymaps, behaviors, features, input processors, configuration, and local toolchain build/flash.
About
A structured reference for ZMK firmware covering getting started, keymaps, behaviors, features like Bluetooth and split keyboards, hardware integration, and the local build/flash toolchain. A developer loads it when building or customizing mechanical keyboard firmware.
- 22 behaviors including hold-tap, macros, tap-dance, mod-morph
- Hardware integration for new boards, shields, and physical layouts
Zmk by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,291 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill zmkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Reference ZMK firmware for keyboard builds covering keymaps, behaviors, features, input processors, configuration, and local toolchain build/flash.
Files
ZMK Firmware リファレンス
ZMK Firmware — Zephyr RTOS 上に構築されたオープンソース (MIT) のキーボードファームウェア。 省電力・無線・分割キーボード対応を主眼に設計され、devicetree ベースの宣言的設定により多様な MCU(nRF52/RP2/SAMD21/STM32 等)と shield をサポートする。 公式ドキュメント (zmk.dev/docs) の全 11 カテゴリを構造化。 keymap 設計・behaviors 選択・config 設定・新規 board/shield 作成・ローカルビルド時に参照する。
ディレクトリ構造
skills/zmk/
├── SKILL.md ← このファイル(エントリーポイント)
└── references/
├── getting-started/README.md ← Getting Started 索引(5 ページ)
├── troubleshooting/README.md ← Troubleshooting 索引(5 ページ)
├── features/README.md ← Features 索引(12 ページ)
├── keymaps/README.md ← Keymaps overview 索引(6 ページ)
├── behaviors/README.md ← Behaviors 索引(23 ページ)
├── input-processors/README.md ← Input Processors 索引(7 ページ)
├── config/README.md ← Configuration 索引(19 ページ)
├── development/README.md ← Development core 索引(6 ページ)
├── contributing/README.md ← Contributing 索引(3 ページ)
├── hardware-integration/README.md ← Hardware Integration 索引(12 ページ)
│ ├── bootloader/README.md ← Bootloader 索引(6 ページ)
│ └── lighting/README.md ← HW Lighting 索引(4 ページ)
└── local-toolchain/README.md ← Local Toolchain 索引(8 ページ)探索手順
1. ユーザーのタスクに最も関連するカテゴリを特定する 2. そのカテゴリの README.md を読む 3. README.md 内の一覧から必要な個別ファイルを選んで読む 4. 必要に応じて関連ページのリンクを辿る
カテゴリ → README.md マッピング
| タスク例 | カテゴリ | README パス |
|---|---|---|
対応ハードウェア確認、インストール手順、ZMK CLI、.conf/.keymap/build.yaml のカスタマイズ、FAQ | getting-started | references/getting-started/README.md |
| ビルド失敗、Bluetooth ペアリング失敗、フラッシュエラー、GPIO/ハードウェア起因の不具合の診断と修正 | troubleshooting | references/troubleshooting/README.md |
| Bluetooth、Split keyboards、Encoders、Displays、Lighting、Pointing、Low Power、Studio、Modules 等の機能概要と有効化 | features | references/features/README.md |
| keymap ファイル構造、keycodes 一覧、modifiers (LC/LS/LA/LG)、combos、conditional layers | keymaps | references/keymaps/README.md |
&kp, &mt, <, &mo, &sk, &sl, &td, ¯o, &mmv/&mwh, &out, &bt, &bl, &rgb_ug, &reset, &soft_off 等の挙動と設定 | behaviors | references/behaviors/README.md |
Pointing デバイス入力の変換(code-mapper, scaler, temp-layer, transformer)、input-processors プロパティ | input-processors | references/input-processors/README.md |
Kconfig (CONFIG_ZMK_*)、devicetree プロパティの完全リファレンス(kscan, split, BLE, encoders, lighting, settings 等) | config | references/config/README.md |
| devicetree 基礎、events システム、Zephyr モジュール作成、新規 behavior 実装、Studio RPC、USB logging | development | references/development/README.md |
| Clean room ポリシー、ドキュメント編集、Conventional Commits 準拠の PR ガイド | contributing | references/contributing/README.md |
| 新規 board/shield 作成、physical layouts、pinctrl、dongle、battery、shift-registers、soft-off setup、bootloader/lighting 統合 | hardware-integration | references/hardware-integration/README.md |
| Adafruit nRF52 / RP2 / SAMD21-UF2 / STM32 / TinyUF2 各 bootloader の対応とフラッシュ手順 | hardware-integration/bootloader | references/hardware-integration/bootloader/README.md |
| Backlight / Underglow (RGB) / LED Indicators のハードウェア統合 | hardware-integration/lighting | references/hardware-integration/lighting/README.md |
Container / Native セットアップ、west build/west flash、IDE 連携、posix-board、pre-commit、tests | local-toolchain | references/local-toolchain/README.md |
Backlight
Controls per-key or single-color LED backlight: power state and brightness level.
Signature / Usage
#include <dt-bindings/zmk/backlight.h>
&bl BL_TOG // toggle on/off
&bl BL_INC // increase brightness
&bl BL_SET 50 // set brightness to 50Parameters: A BL_* command constant, and optionally a brightness value for BL_SET.
Options / Props
| Command | Parameters | Action |
|---|---|---|
BL_ON | — | Enable backlight |
BL_OFF | — | Disable backlight |
BL_TOG | — | Toggle on/off |
BL_INC | — | Increase brightness |
BL_DEC | — | Decrease brightness |
BL_CYCLE | — | Cycle through brightness levels |
BL_SET | brightness value | Set a specific brightness value |
Notes
- Settings persist across restarts and firmware flashes (saved to flash storage).
- Once changed, the new values override the
CONFIG_ZMK_BACKLIGHT_*_STARTdefaults. - On split keyboards this is a global behavior: changes affect both central and peripheral sides.
Related
- RGB Underglow
- Power
Bluetooth
Manages Bluetooth connection profiles between the keyboard and host devices. ZMK supports up to five BT profiles by default.
Signature / Usage
#include <dt-bindings/zmk/bt.h>
&bt BT_CLR // clear bond on current profile
&bt BT_NXT // switch to next profile
&bt BT_PRV // switch to previous profile
&bt BT_SEL 0 // select profile 0 (0-indexed)
&bt BT_DISC 1 // disconnect profile 1
&bt BT_CLR_ALL // clear bonds on all profilesParameters: A BT_* command constant, and optionally a profile index for BT_SEL / BT_DISC.
Options / Props
| Command | Parameters | Action |
|---|---|---|
BT_CLR | — | Clear bond info for the currently selected profile |
BT_CLR_ALL | — | Clear bond info for all profiles |
BT_NXT | — | Switch to next profile (wraps around) |
BT_PRV | — | Switch to previous profile (wraps around) |
BT_SEL | profile index (0-based) | Select a specific profile |
BT_DISC | profile index (0-based) | Disconnect the specified profile if currently inactive |
Notes
- Multiple hosts can appear "connected" simultaneously; only the active profile receives keystrokes.
- To fully remove a bond, clear it on the keyboard and on the host device to prevent connection failures.
- The active profile reconnects immediately if disconnected, even when USB output is selected.
- Profile selection persists across restarts (saved to flash); debounce controlled by
CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE. - Adjust max profiles via
CONFIG_BT_MAX_CONNandCONFIG_BT_MAX_PAIREDin the.conffile.
Related
- Output Selection
- Reset
Caps Word
A smart caps-lock variant that capitalizes alphabetic keys and automatically deactivates when a key outside the continue list is pressed, or when the behavior is triggered again.
Signature / Usage
&caps_wordCustom configuration:
&caps_word {
continue-list = <UNDERSCORE MINUS>;
};Custom named instance:
/ {
behaviors {
prog_caps: prog_caps {
compatible = "zmk,behavior-caps-word";
#binding-cells = <0>;
continue-list = <UNDERSCORE>;
};
};
};
// Usage: &prog_capsOptions / Props
| Property | Type | Description |
|---|---|---|
continue-list | array of keycodes | Keys that keep caps word active; overrides the default list (alphanumeric, _, backspace, delete) |
mods | modifier bitmask | Modifier applied to alphabetic keys while active (default: MOD_LSFT) |
Notes
- The shift modifier applies only to alphabetic keycodes (A–Z); numbers and symbols pass through unmodified.
- Useful with mod-tap on home-row keys to avoid alternating holds when typing all-caps words.
- Multiple instances with different
continue-listvalues enable distinct caps-word modes on the same keyboard.
Related
- Tap-Dance
- Key Repeat
- Hold-Tap / Mod-Tap
Hold-Tap
Sends one behavior when a key is held and a different behavior when it is tapped. ZMK ships two pre-configured instances: &mt (mod-tap) and < (layer-tap).
Signature / Usage
// Pre-built instances
&mt <MODIFIER> <keycode> // e.g. &mt LSHIFT A
< <layer> <keycode> // e.g. < 1 SPACE
// Custom instance defined in behaviors {}
/ {
behaviors {
hm: homerow_mods {
compatible = "zmk,behavior-hold-tap";
#binding-cells = <2>;
tapping-term-ms = <150>;
flavor = "balanced";
bindings = <&kp>, <&kp>;
};
};
};
// Usage: &hm LSHIFT AParameters: Two values — (1) behavior/keycode for hold, (2) behavior/keycode for tap.
Pre-built Instances
| Binding | Hold default | Tap default | Default flavor |
|---|---|---|---|
&mt | modifier keycode | keycode | hold-preferred |
< | layer activation | keycode | tap-preferred |
Options / Props
| Property | Type | Description |
|---|---|---|
tapping-term-ms | integer | ms before hold triggers (default: 200) |
flavor | string | Interrupt handling style (see below) |
quick-tap-ms | integer | If re-pressed within this ms, always tap |
require-prior-idle-ms | integer | Force tap if pressed within X ms of last key |
hold-trigger-key-positions | array | Key positions that allow hold activation (positional hold-tap) |
hold-trigger-on-release | boolean | Evaluate trigger positions on key release instead of press |
hold-while-undecided | boolean | Immediately activate hold; release before tap |
hold-while-undecided-linger | boolean | Keep hold active until after tap behavior completes |
retro-tap | boolean | Trigger tap if key released before another key was pressed |
Flavors
| Flavor | Hold triggers when… |
|---|---|
hold-preferred | tapping-term-ms expires or another key is pressed |
balanced | tapping-term-ms expires or another key is pressed and released |
tap-preferred | tapping-term-ms expires only (other presses don't affect decision) |
tap-unless-interrupted | Another key is pressed before tapping-term-ms expires |
Notes
- Hold-tap cannot directly bind behaviors requiring multiple parameters (e.g.,
&bt BT_SEL 1); wrap them in a macro first. - Key positions are zero-indexed, sequentially numbered from the first key in the keymap.
hold-while-undecidedinteracts with combos — combo timeouts must expire before hold activates.- Common pattern: homerow mods using
balancedflavor withhold-trigger-key-positionsandrequire-prior-idle-ms.
Related
- Key Press
- Layers
- Macros
Key Press
Sends a standard HID keycode on press and releases it on key release. This is the most fundamental ZMK behavior.
Signature / Usage
&kp <keycode>Parameter: A keycode constant from dt-bindings/zmk/keys.h (e.g., A, N1, LSHIFT, C_VOL_UP).
#include <dt-bindings/zmk/keys.h>
/ {
keymap {
default_layer {
bindings = <&kp A &kp LSHIFT &kp C_VOL_UP>;
};
};
};Notes
- Keycodes span multiple HID usage pages: keyboard keys, modifiers, keypad, media/consumer controls, power, and application shortcuts.
- Include
<dt-bindings/zmk/keys.h>for human-readable constants. - Raw HID usage IDs can be used directly if the constant is not defined in the header.
Related
- Hold-Tap
- Sticky Key
- Mod-Morph
Key Repeat
Resends whatever keycode was last sent. Useful for keyboards that lack a dedicated repeat key.
Signature / Usage
&key_repeatNo parameters needed for basic use.
Custom configuration to also track consumer keys:
&key_repeat {
usage-pages = <HID_USAGE_KEY HID_USAGE_CONSUMER>;
};Options / Props
| Property | Type | Description |
|---|---|---|
usage-pages | list of HID usage page constants | Which HID usage pages to track for repeat; default <HID_USAGE_KEY> |
Notes
- By default, only HID Key usage page events are tracked; consumer page events (media keys) are ignored unless
HID_USAGE_CONSUMERis added. - Multiple usage pages can be listed together.
Related
- Caps Word
- Key Press
Key Toggle
Toggles a key between pressed and released state. Pressing when the key is up holds it down; pressing again releases it. Useful for shift-lock or sustained modifier keys.
Signature / Usage
&kt <keycode>
// e.g. &kt LALTParameter: Any keycode compatible with &kp (e.g., LALT, DOWN_ARROW).
Custom one-directional instance:
/ {
behaviors {
kt_on: key_toggle_on_only {
compatible = "zmk,behavior-key-toggle";
#binding-cells = <1>;
display-name = "Key Toggle On";
toggle-mode = "on";
};
};
};
// Usage: &kt_on LSHIFTOptions / Props
| Property | Type | Description |
|---|---|---|
toggle-mode | string ("on" / "off") | Restrict to only toggle on or only toggle off instead of alternating |
Notes
- Modified keys like
LA(A)toggle based on the base keycode only; the modifier wrapper is disregarded when checking press state.
Related
- Key Press
- Sticky Key
Layer Behaviors
ZMK provides several behaviors for activating, toggling, and switching keyboard layers. Multiple layers can be active simultaneously; layer numbers start at 0 in keymap definition order.
Signature / Usage
&mo <layer> // Momentary: active while held
&to <layer> // To: enable one layer, disable all others (except default)
&tog <layer> // Toggle: flip current on/off state
< <layer> <keycode> // Layer-Tap: layer on hold, keycode on tap (see hold-tap.md)Behaviors
Momentary Layer — &mo
Activates a layer while the key is held; deactivates on release.
&mo 3 // hold to activate layer 3To Layer — &to
Enables the specified layer and disables all others except the default (layer 0).
&to 3Toggle Layer — &tog
Flips the layer between enabled and disabled.
&tog 3Custom one-directional variant:
/ {
behaviors {
tog_on: toggle_layer_on {
compatible = "zmk,behavior-toggle-layer";
#binding-cells = <1>;
display-name = "Toggle Layer On";
toggle-mode = "on";
};
};
};Options / Props (&tog)
| Property | Type | Description |
|---|---|---|
toggle-mode | string ("on" / "off") | Restrict behavior to only enable or only disable the layer |
locking | flag | Mark this layer activation as "locked" (won't be deactivated by non-locking behaviors) |
Notes
&toand&togwithlockingproperty prevent momentary behaviors from deactivating those layers.- Define layer number constants for readability:
#define LOWER 1, then use&mo LOWER. - Layer-Tap (
<) is documented in detail under Hold-Tap. - Conditional Layers (activate a layer when two other layers are both active) are configured separately in the
conditional_layersnode.
Related
- Hold-Tap / Layer-Tap
- Sticky Layer
- Misc (trans / none)
Macros
Defines a sequence of behaviors to invoke when the macro key is pressed and/or released.
Signature / Usage
#include <dt-bindings/zmk/keys.h>
/ {
macros {
zed_em_kay: zed_em_kay {
compatible = "zmk,behavior-macro";
#binding-cells = <0>;
bindings =
<¯o_press &kp LSHFT>,
<¯o_tap &kp Z &kp M &kp K>,
<¯o_release &kp LSHFT>;
};
};
};
// Usage in keymap bindings:
// &zed_em_kayOptions / Props
| Property | Type | Description |
|---|---|---|
bindings | phandle-array | Ordered list of behaviors (and control tokens) to execute |
wait-ms | integer | Delay (ms) between behaviors; default from CONFIG_ZMK_MACRO_DEFAULT_WAIT_MS |
tap-ms | integer | Duration a tapped behavior is held; default from CONFIG_ZMK_MACRO_DEFAULT_TAP_MS |
Macro Control Behaviors
| Control | Action |
|---|---|
¯o_tap | Press then release each behavior (default mode) |
¯o_press | Press without releasing |
¯o_release | Release previously pressed behaviors |
¯o_pause_for_release | Pause; resume remaining bindings on macro key release |
¯o_wait_time <ms> | Change wait-ms mid-sequence |
¯o_tap_time <ms> | Change tap-ms mid-sequence |
Parameterized Macros
| Compatible | #binding-cells | Description |
|---|---|---|
zmk,behavior-macro | <0> | No parameters |
zmk,behavior-macro-one-param | <1> | One parameter passed to inner behavior |
zmk,behavior-macro-two-param | <2> | Two parameters passed to inner behaviors |
Parameter forwarding controls: ¯o_param_1to1, ¯o_param_1to2, ¯o_param_2to1, ¯o_param_2to2. Use MACRO_PLACEHOLDER as a dummy value where parameters will be substituted.
Notes
- Behavior queue size defaults to 64; increase with
CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE. - Maximum bindings per
bindingsfield: 256. - Use macros to wrap multi-parameter behaviors (e.g.,
&bt BT_SEL 1) for use inside hold-tap.
Related
- Key Press
- Hold-Tap
Miscellaneous Behaviors
Two utility behaviors for controlling key event propagation between layers.
Signature / Usage
&trans // pass event to next active layer
&none // swallow and block eventNo parameters for either behavior.
Behaviors
Transparent — &trans
Ignores the key press/release at this layer position, allowing the event to fall through to the next active layer in the stack.
bindings = <&trans &kp A &trans>;None — &none
Swallows the key press/release at this layer position, preventing it from reaching any lower layer.
bindings = <&none &kp A &none>;Notes
&transis the standard placeholder for keys that should "inherit" their binding from a lower layer.&noneexplicitly blocks a key position, useful for disabling keys on a specific layer without accidentally activating bindings from lower layers.
Related
- Layers
- Overview
Mod-Morph
Invokes one of two behaviors depending on whether specified modifier keys are held at the time of the key press. ZMK ships a pre-configured &gresc (Grave Escape) instance.
Signature / Usage
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/mod-morph.h>
/ {
behaviors {
gresc: grave_escape {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
bindings = <&kp ESC>, <&kp GRAVE>;
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
};
};
};
// Usage: &grescParameters: None (zero-parameter behavior; behavior is fixed at definition time).
Options / Props
| Property | Type | Description |
|---|---|---|
bindings | phandle-array (2) | [unmodified_behavior, morphed_behavior] |
mods | bitmask | Modifier(s) that trigger the morphed binding |
keep-mods | bitmask | Modifiers to pass through with the morphed keycode (default: 0, all suppressed) |
Available Modifier Constants
MOD_LSFT, MOD_RSFT, MOD_LCTL, MOD_RCTL, MOD_LALT, MOD_RALT, MOD_LGUI, MOD_RGUI
Combine with |: (MOD_LSFT|MOD_RSFT)
Notes
- Mod-morphs can be nested for complex multi-modifier logic.
- macOS users may need to disable "Modify Events" in Karabiner-Elements to avoid conflicts.
Related
- Key Press
- Hold-Tap
- Macros
Mouse Emulation
Three behaviors for controlling the mouse pointer, buttons, and scroll wheel from the keyboard. Requires the pointing feature to be enabled.
Signature / Usage
#include <dt-bindings/zmk/pointing.h>
// Mouse button press
&mkp MB1 // left click
&mkp RCLK // right click
// Mouse movement
&mmv MOVE_UP
&mmv MOVE_X(500)
// Scroll
&msc SCRL_DOWNEnable feature: Add CONFIG_ZMK_POINTING=y to your .conf file.
Behaviors
Mouse Button Press — &mkp
| Button | Alias | Description |
|---|---|---|
MB1 | LCLK | Left click |
MB2 | RCLK | Right click |
MB3 | MCLK | Middle click |
MB4 | Button 4 | |
MB5 | Button 5 |
Input listener: &mkp_input_listener
Mouse Move — &mmv
Transmits X/Y movement events to the host.
| Predefined value | Description |
|---|---|
MOVE_UP | Move pointer up |
MOVE_DOWN | Move pointer down |
MOVE_LEFT | Move pointer left |
MOVE_RIGHT | Move pointer right |
Custom speed: MOVE_X(value) / MOVE_Y(value) (signed 16-bit integer).
Advanced listener properties: x-input-code, y-input-code, time-to-max-speed-ms, acceleration-exponent.
Input listener: &mmv_input_listener
Mouse Scroll — &msc
Sends vertical and horizontal scroll events.
| Predefined value | Description |
|---|---|
SCRL_UP | Scroll up |
SCRL_DOWN | Scroll down |
SCRL_LEFT | Scroll left |
SCRL_RIGHT | Scroll right |
Input listener: &msc_input_listener
Notes
- Enabling
CONFIG_ZMK_POINTING=ymodifies the HID report descriptor; the host OS must re-enumerate the device (replug or re-pair after firmware flash). - The
uint32parameter for&mmvand&mscencodes X velocity in the upper 16 bits and Y velocity in the lower 16 bits.
Output Selection
Selects which connection (USB or Bluetooth) receives keyboard input when both are simultaneously active.
Signature / Usage
#include <dt-bindings/zmk/outputs.h>
&out OUT_USB // route to USB
&out OUT_BLE // route to current BT profile
&out OUT_TOG // toggle between USB and BLE
&out OUT_NONE // disable all outputParameter: One of the OUT_* command constants.
Options / Props
| Command | Action |
|---|---|
OUT_USB | Route output to USB connection |
OUT_BLE | Route output to the active Bluetooth profile |
OUT_TOG | Toggle between USB and BLE |
OUT_NONE | Disable all output transmission |
Notes
- USB is the default output when both connections exist.
- The selected output persists across restarts and firmware flashes (saved to flash storage).
- Settings are saved after
CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCEms to minimize flash wear.
Related
- Bluetooth
- RGB Underglow
Behaviors Overview
Behaviors are the actions assigned to key positions, sensors, and combos in ZMK keymaps. They define what happens when a key is pressed or released, or when an encoder rotates. Behaviors can also invoke other behaviors recursively (e.g., inside macros).
Signature / Usage
Behaviors are referenced in keymap bindings using an ampersand prefix:
/ {
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp A &mo 1 &bt BT_NXT
>;
};
};
};Behavior Categories
| Category | Bindings | Description |
|---|---|---|
| Key Press | &kp | Send standard HID keycodes |
| Hold-Tap | &mt, < | Different action on hold vs. tap |
| Layers | &mo, &to, &tog, &sl | Layer activation and switching |
| Sticky | &sk, &sl | One-shot key/layer behaviors |
| Tap-Dance | &td | Different action per tap count |
| Mod-Morph | &gresc | Different action based on held modifier |
| Macros | ¯o_* | Sequences of behaviors |
| Mouse | &mkp, &mmv, &msc | Mouse button, move, scroll |
| Lighting | &rgb_ug, &bl | RGB underglow and backlight |
| Bluetooth | &bt | BT profile management |
| Output | &out | USB/BLE output selection |
| Power | &ext_power, &soft_off | External power and soft-off |
| Reset | &sys_reset, &bootloader | Firmware reset and bootloader |
| Misc | &trans, &none | Transparent passthrough and block |
| Sensor | &inc_dec_kp | Encoder rotation handling |
Notes
- Trigger types: key press/release on a layer, sensor rotation event, combo activation.
- Behaviors requiring multiple parameters (e.g.,
&bt BT_SEL 1) cannot be used directly as hold-tap bindings; use a macro wrapper instead. - Custom behavior instances (e.g., custom hold-tap flavors) are defined in the devicetree
behaviors {}block and referenced by their label.
Related
- Key Press
- Hold-Tap
- Layers
- Macros
External Power Control
Enables or disables the VCC power output to external devices (e.g., RGB LEDs) to extend battery life on supported controllers.
Signature / Usage
#include <dt-bindings/zmk/ext_power.h>
&ext_power EP_ON // enable external power
&ext_power EP_OFF // disable external power
&ext_power EP_TOG // toggle external powerParameter: One of the EP_* (or full EXT_POWER_*_CMD) constants.
Options / Props
| Command | Alias | Action |
|---|---|---|
EXT_POWER_ON_CMD | EP_ON | Enable external power output |
EXT_POWER_OFF_CMD | EP_OFF | Disable external power output |
EXT_POWER_TOGGLE_CMD | EP_TOG | Toggle external power output |
Notes
- Supported hardware: nRFMicro, nice!nano.
- Settings persist across restarts and firmware flashes (saved to flash).
- On split keyboards this is a global behavior: changes affect both central and peripheral sides.
- Changes are saved after
CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCEms to reduce flash wear.
Related
- Backlight
- RGB Underglow
- Soft Off
Keymap Behaviors
| Name | Binding(s) | Description | Path |
|---|---|---|---|
| Overview | — | What behaviors are and how they are used in ZMK keymaps | overview.md |
| Key Press | &kp | Send standard HID keycodes | key-press.md |
| Hold-Tap | &mt, < | Different action on hold vs. tap; mod-tap and layer-tap | hold-tap.md |
| Layers | &mo, &to, &tog | Momentary, to-layer, and toggle layer activation | layers.md |
| Macros | ¯o_* | Sequences of behaviors executed on key press/release | macros.md |
| Mod-Morph | &gresc | Different action depending on held modifiers | mod-morph.md |
| Sticky Key | &sk | One-shot modifier key (stays active until next keypress) | sticky-key.md |
| Sticky Layer | &sl | One-shot layer activation | sticky-layer.md |
| Tap-Dance | &td | Different action per consecutive tap count | tap-dance.md |
| Mouse Emulation | &mkp, &mmv, &msc | Mouse button, pointer movement, and scroll | mouse-emulation.md |
| Key Repeat | &key_repeat | Resend the last transmitted keycode | key-repeat.md |
| Key Toggle | &kt | Toggle a key between pressed and released state | key-toggle.md |
| Caps Word | &caps_word | Smart caps-lock that auto-deactivates on non-word keys | caps-word.md |
| Output Selection | &out | Choose between USB and Bluetooth output | outputs.md |
| Bluetooth | &bt | Manage Bluetooth connection profiles | bluetooth.md |
| Backlight | &bl | Control per-key LED backlight brightness | backlight.md |
| RGB Underglow | &rgb_ug | Control RGB underglow color, brightness, and effects | underglow.md |
| External Power | &ext_power | Toggle VCC power to external peripherals (battery saving) | power.md |
| Reset | &sys_reset, &bootloader | Restart firmware or enter bootloader mode | reset.md |
| Soft Off | &soft_off | Force keyboard into deep low-power off state | soft-off.md |
| ZMK Studio Unlock | &studio_unlock | Unlock device for live editing via ZMK Studio | studio-unlock.md |
| Sensor Rotation | &inc_dec_kp / custom | Encoder clockwise/counter-clockwise behavior | sensor-rotate.md |
| Miscellaneous | &trans, &none | Transparent passthrough and event block | misc.md |
Reset
Two behaviors for restarting the keyboard firmware or entering bootloader mode for flashing.
Signature / Usage
&sys_reset // restart firmware
&bootloader // enter bootloader modeNo parameters for either behavior.
Behaviors
System Reset — &sys_reset
Resets the keyboard and re-runs the firmware currently flashed to the device.
Bootloader Reset — &bootloader
Resets the keyboard into bootloader mode, making it appear as a mass-storage device for firmware flashing.
Notes
- Both behaviors are source-specific on split keyboards: they only affect the half where the binding is activated.
- To reset both halves, place the bindings on both sides of the split.
- Combos always invoke reset behavior bindings on the central part of the split keyboard, regardless of which side physically triggers the combo.
Related
- Bluetooth
- Soft Off
Sensor Rotation
Triggers different behaviors depending on the direction (clockwise or counter-clockwise) of an encoder rotation. Two variants exist: a fixed-binding standard form and a parameterized variable form.
Signature / Usage
Standard — zmk,behavior-sensor-rotate (#sensor-binding-cells = <0>)
Behaviors are fully defined in the behaviors {} node:
/ {
behaviors {
rgb_encoder: rgb_encoder {
compatible = "zmk,behavior-sensor-rotate";
#sensor-binding-cells = <0>;
bindings = <&rgb_ug RGB_BRI>, <&rgb_ug RGB_BRD>;
};
};
keymap {
default_layer {
sensor-bindings = <&rgb_encoder>;
};
};
};Variable — zmk,behavior-sensor-rotate-var (#sensor-binding-cells = <2>)
Two parameters are supplied at the keymap binding site:
/ {
behaviors {
rot_kp: rot_kp {
compatible = "zmk,behavior-sensor-rotate-var";
#sensor-binding-cells = <2>;
bindings = <&kp>, <&kp>;
};
};
keymap {
default_layer {
sensor-bindings = <&rot_kp PG_UP PG_DN>;
};
};
};Options / Props
| Property | Type | Description |
|---|---|---|
compatible | string | zmk,behavior-sensor-rotate or zmk,behavior-sensor-rotate-var |
#sensor-binding-cells | integer | 0 for standard, 2 for variable |
bindings | phandle-array | [clockwise_behavior, counter_clockwise_behavior] |
sensor-bindings | phandle + params | Sensor binding reference (with optional params for variable variant) |
Notes
- Clockwise rotation invokes the first binding; counter-clockwise invokes the second.
- The variable variant passes its two keymap-site parameters directly to the behaviors defined in
bindings.
Related
- Key Press
- RGB Underglow
Soft Off
Forces the keyboard into a deep low-power off state. The device can be reactivated with a dedicated on/off button (if present) or the reset button.
Signature / Usage
&soft_offOptional hold-to-activate configuration:
&soft_off {
hold-time-ms = <5000>;
};Options / Props
| Property | Type | Description |
|---|---|---|
hold-time-ms | integer | Key must be held this many ms before soft off activates; default: activates on release (no hold required) |
split-peripheral-off-on-press | flag | Peripheral side of a split enters soft off immediately on press, ignoring hold-time-ms; improves reliability |
Notes
- Requires soft-off to be enabled in the device configuration.
- On split keyboards, peripheral halves always activate immediately when the behavior triggers for reliability; the
/delete-property/directive can removesplit-peripheral-off-on-pressif you accept the reliability trade-off.
Related
- Power
- Reset
Sticky Key
Keeps a key (typically a modifier) active until the next non-modifier key is pressed, then releases it automatically. Equivalent to QMK "one-shot mods".
Signature / Usage
&sk <keycode>
// e.g. &sk LSHIFTParameter: A keycode (typically a modifier such as LSHIFT, LCTRL, or a chained modifier like LG(LS(LA(LCTRL)))).
Custom instance example:
/ {
behaviors {
skq: sticky_key_quick_release {
compatible = "zmk,behavior-sticky-key";
#binding-cells = <1>;
bindings = <&kp>;
release-after-ms = <1000>;
quick-release;
ignore-modifiers;
};
};
};Options / Props
| Property | Type | Description |
|---|---|---|
release-after-ms | integer | Auto-deactivate after this ms if no other key is pressed (default: 1000) |
quick-release | flag | Deactivate on next key press rather than release; prevents modifier chaining during rapid typing |
lazy | flag | Activate the sticky key immediately before another key press instead of on initial press; avoids unintended menu triggers |
ignore-modifiers | flag | Allow chaining multiple sticky modifiers before they release (enabled by default) |
Notes
- Sticky keys can be chained: tap
&sk LCTRL, tap&sk LSHIFT, tap&kp A→ produces Ctrl+Shift+A. lazysticky keys don't trigger other behavior releases without additional configuration.
Related
- Sticky Layer
- Key Press
Sticky Layer
Activates a layer temporarily without requiring the key to be held. The layer stays active until another (non-modifier) key is pressed, then deactivates. Equivalent to QMK "one-shot layers".
Signature / Usage
&sl <layer>
// e.g. &sl 1Parameter: Layer index to activate (0-based).
Custom configuration example:
&sl {
release-after-ms = <2000>;
};Options / Props
| Property | Type | Description |
|---|---|---|
release-after-ms | integer | Auto-release timeout in ms if no key is pressed (default: 1000) |
quick-release | flag | Layer releases immediately on next key press (enabled by default) |
Notes
- Can be chained with sticky keys: tap
&sl 1, then&sk LSHIFT, then&kp A→ produces Shift+A on layer 1.
Related
- Sticky Key
- Layers
- Tap-Dance
ZMK Studio Unlock
Unlocks the device for live editing via ZMK Studio. The device remains unlocked until an inactivity timeout or disconnection occurs.
Signature / Usage
&studio_unlockNo parameters.
Notes
- Unlock duration is controlled by the inactivity timeout configured in studio settings.
- Disconnecting from ZMK Studio automatically re-locks the device.
- This is a development/experimental feature; see the ZMK Studio documentation for setup requirements.
Related
- Soft Off
- Reset
Tap-Dance
Activates different behaviors based on how many times the key is tapped in quick succession.
Signature / Usage
/ {
behaviors {
td0: tap_dance_0 {
compatible = "zmk,behavior-tap-dance";
#binding-cells = <0>;
tapping-term-ms = <200>;
bindings = <&kp N1>, <&kp N2>, <&kp N3>;
};
};
};
// Usage in keymap: &td0
// 1 tap → N1, 2 taps → N2, 3 taps → N3Parameters: None (zero-parameter; behavior is fully defined at the behaviors node).
Options / Props
| Property | Type | Description |
|---|---|---|
tapping-term-ms | integer | Max time between taps before a binding is selected (default: 200) |
bindings | phandle-array | Behaviors indexed by tap count (index 0 = 1 tap, index 1 = 2 taps, …) |
Notes
- Tap-dances resolve immediately when interrupted by another keypress.
- When the maximum tap count is reached, the last binding fires immediately.
- Modifier keys held during a tap-dance remain active until the tap-dance is released, affecting subsequent keypresses.
Related
- Hold-Tap
- Sticky Key
- Mod-Morph
RGB Underglow
Controls RGB underglow LEDs: power, hue, saturation, brightness, animation speed, and color selection.
Signature / Usage
#include <dt-bindings/zmk/rgb.h>
&rgb_ug RGB_TOG // toggle on/off
&rgb_ug RGB_BRI // increase brightness
&rgb_ug RGB_COLOR_HSB(128, 100, 100) // set greenParameters: An RGB_* action constant, and optionally an HSB value for RGB_COLOR_HSB.
Options / Props
| Command | Action |
|---|---|
RGB_ON | Enable RGB underglow |
RGB_OFF | Disable RGB underglow |
RGB_TOG | Toggle on/off |
RGB_HUI | Increase hue |
RGB_HUD | Decrease hue |
RGB_SAI | Increase saturation |
RGB_SAD | Decrease saturation |
RGB_BRI | Increase brightness |
RGB_BRD | Decrease brightness |
RGB_SPI | Increase animation speed |
RGB_SPD | Decrease animation speed |
RGB_EFF | Cycle effect forward |
RGB_EFR | Cycle effect reverse |
RGB_COLOR_HSB(h, s, b) | Set a specific HSB (HSV) color |
HSB Value Ranges
| Channel | Range |
|---|---|
| Hue | 0–360 |
| Saturation | 0–100 |
| Brightness | 0–100 |
Notes
- RGB settings persist across restarts and firmware flashes; they override
CONFIG_ZMK_RGB_*_STARTdefaults. - Changes are saved after
CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCEms to reduce flash wear. - On split keyboards this is a global behavior: changes affect both central and peripheral sides.
Related
- Backlight
- Power
- Output Selection
Battery
Enables detection and reporting of keyboard battery status, including peripheral battery levels for wireless split keyboards.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BATTERY_REPORTING | bool | n | Enable battery detection and reporting |
CONFIG_ZMK_BATTERY_REPORT_INTERVAL | int | 60 | Reporting interval in seconds |
Devicetree
/chosen node
| Property | Type | Description |
|---|---|---|
zmk,battery | path | Specifies the battery sensor driver to use |
Battery Voltage Divider Sensor
Compatible: zmk,battery-voltage-divider
Reads battery voltage via ADC and voltage divider. Follows Zephyr's voltage divider documentation for properties.
nRF VDDH Battery Sensor
Compatible: zmk,battery-nrf-vddh
Reads battery voltage using Nordic nRF52's VDDH pin. No additional configuration required.
Notes
- Battery reporting is automatically enabled when BLE is active, unless explicitly disabled.
- On macOS, battery reporting packets can wake the computer from sleep. Disable with
CONFIG_BT_BAS=nto prevent this while retaining monitoring. - For wireless split keyboards, enable both
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY=yandCONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=yto report levels from both halves.
Related
- Split
- Power
- System
Behaviors
Configurable properties that adjust how keyboard actions function. Behaviors serve as templates for custom actions; built-in nodes can be overridden in your keymap file.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE | int | 64 | Maximum number of behaviors to queue from a macro or other complex behavior |
CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD | int | 10 | Max simultaneous held hold-tap behaviors |
CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS | int | 40 | Max captured events for hold-tap |
CONFIG_ZMK_MACRO_DEFAULT_WAIT_MS | int | 15 | Default wait time between macro steps |
CONFIG_ZMK_MACRO_DEFAULT_TAP_MS | int | 30 | Default tap duration for macro key presses |
CONFIG_ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD | int | 10 | Max simultaneous held sticky-key behaviors |
CONFIG_ZMK_BEHAVIOR_TAP_DANCE_MAX_HELD | int | 10 | Max simultaneous held tap-dance behaviors |
Devicetree
Common Property
| Property | Type | Description |
|---|---|---|
display-name | string | Layer name for display or ZMK Studio |
Hold-Tap (zmk,behavior-hold-tap)
Default nodes: < (layer-tap), &mt (mod-tap).
| Property | Type | Default | Description |
|---|---|---|---|
tapping-term-ms | int | — | Milliseconds before triggering hold |
quick-tap-ms | int | -1 | Window for double-tap to force tap; -1 disables |
flavor | string | — | One of: hold-preferred, balanced, tap-preferred, tap-unless-interrupted |
hold-trigger-key-positions | array | — | Key indices that trigger tap when non-listed keys are pressed |
Macro (zmk,behavior-macro / zmk,behavior-macro-one-param / zmk,behavior-macro-two-param)
#binding-cells | Compatible |
|---|---|
| 0 | zmk,behavior-macro |
| 1 | zmk,behavior-macro-one-param |
| 2 | zmk,behavior-macro-two-param |
Control behaviors: ¯o_tap, ¯o_press, ¯o_release, ¯o_pause_for_release, ¯o_wait_time, ¯o_param_*
Sticky Key (zmk,behavior-sticky-key)
Default nodes: &sk (sticky key), &sl (sticky layer).
| Property | Type | Default | Description |
|---|---|---|---|
release-after-ms | int | 1000 | Release timeout in milliseconds |
quick-release | bool | false | Release before the next key is released |
lazy | bool | false | Do not activate until another key is pressed |
ignore-modifiers | bool | true | Do not release when a modifier is pressed |
Tap Dance (zmk,behavior-tap-dance)
| Property | Type | Default | Description |
|---|---|---|---|
tapping-term-ms | int | 200 | Max time between taps before triggering a binding |
Caps Word (zmk,behavior-caps-word)
| Property | Type | Default | Description |
|---|---|---|---|
continue-list | array | <UNDERSCORE BACKSPACE DELETE> | Keycodes that do not deactivate caps word |
mods | int | <MOD_LSFT> | Modifier bit field to apply |
Two Axis Input (zmk,behavior-input-two-axis)
| Property | Type | Default | Description |
|---|---|---|---|
x-input-code | int | — | Relative event code for X axis |
y-input-code | int | — | Relative event code for Y axis |
trigger-period-ms | int | 16 | Period between input event triggers |
acceleration-exponent | int | 1 | Acceleration curve exponent |
Notes
- Binding definitions are in
app/dts/bindings/behaviors/in the ZMK repository. - All behavior configuration is compile-time; re-flash after changes.
Related
- Keymap
- Combos
- Overview
Bluetooth
Configuration for Bluetooth connectivity, pairing security, and device appearance in ZMK.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BLE_EXPERIMENTAL_CONN | bool | n | Enables connection settings planned for future default |
CONFIG_ZMK_BLE_EXPERIMENTAL_SEC | bool | n | Enables BT Secure Connection passkey entry and key overwrite from previously paired hosts |
CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES | bool | n | Aggregate setting enabling both experimental connection and security configs |
CONFIG_ZMK_BLE_PASSKEY_ENTRY | bool | n | Enable passkey entry during pairing for enhanced security |
CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION | bool | y | Low-level GATT subscription enforcement; set to n to work around Windows battery notification issues |
CONFIG_BT_DEVICE_APPEARANCE | int | 961 | Bluetooth device appearance value (hex converted to decimal) |
Notes
- After enabling
CONFIG_ZMK_BLE_PASSKEY_ENTRY, all previously paired devices must be re-paired. CONFIG_BT_DEVICE_APPEARANCEaccepts the decimal equivalent of Bluetooth appearance hex codes (e.g.,0x03C1= 961 for keyboard).- For system-level BLE settings (max connections, paired devices), see System.
Related
- System
- Split
- Settings
Bootloader
Configuration for bootloader integration, including double-tap reset entry, STM32 option byte setup, and magic value bootmode mapping.
Kconfig
Double Tap To Bootloader
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_DBL_TAP_BOOTLOADER | bool | enabled on STM32/RP2040/RP2350 | Enable double-tap reset to enter bootloader |
CONFIG_ZMK_DBL_TAP_BOOTLOADER_TIMEOUT_MS | int | 500 | Duration in ms to wait for a second reset to enter bootloader |
STM32 nBOOT_SEL Option Byte Setup
Manages the nBOOT_SEL option byte on STM32C0/STM32G0 chips that can block system ROM bootloader access.
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BOOT_STM32_ENFORCE_NBOOT_SEL | bool | enabled on STM32C0/STM32G0 | Ensure the nBOOT_SEL bit is not set |
Bootmode Magic Value Mapper
For SoCs using magic value mappings to select bootloader mode.
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BOOTMODE_BOOTLOADER_MAGIC_VALUE | hex | — | Magic value for retained memory bootloader boot mode |
CONFIG_ZMK_BOOTMODE_MAGIC_VALUE_BOOTLOADER_TYPE_TINYUF2 | bool | false | Default magic value for TinyUF2 bootloader |
CONFIG_ZMK_BOOTMODE_MAGIC_VALUE_BOOTLOADER_TYPE_ADAFRUIT_BOSSA | bool | false | Default magic value for Adafruit BOSSA (SAMD21) bootloader |
CONFIG_ZMK_BOOTMODE_MAGIC_VALUE_BOOTLOADER_TYPE_ADAFRUIT_NRF52 | bool | false | Default magic value for Adafruit nRF52 bootloader |
Notes
- Definition file:
zmk/app/src/boot/Kconfig - Double-tap bootloader is automatically enabled for STM32, RP2040, and RP2350 targets; explicitly disable if not desired.
Related
- System
- Overview
Combos
Configuration for key combos, which trigger a single action when multiple keys are pressed simultaneously within a time window.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS | int | 4 | Maximum number of combos that can be active at the same time |
Devicetree
Compatible: zmk,combos
The parent node has no properties itself; it contains child nodes for each combo.
Combo Child Node Properties
| Property | Type | Default | Description |
|---|---|---|---|
bindings | phandle-array | required | Behavior to execute when the combo triggers |
key-positions | array | required | Key position indices that activate the combo |
timeout-ms | int | 50 | Time window (ms) within which all keys must be pressed |
require-prior-idle-ms | int | -1 | Prevents combo if a non-modifier key was pressed recently; -1 disables |
slow-release | bool | false | Release combo when all keys release (vs. any single key) |
layers | array | all layers | Specific layer indices where the combo is active |
Example
/ {
combos {
compatible = "zmk,combos";
combo_esc {
timeout-ms = <50>;
key-positions = <0 1>;
bindings = <&kp ESC>;
};
};
};Notes
- Omitting
layersenables the combo on all layers. require-prior-idle-msis useful to prevent accidental combo activation during fast typing.
Related
- Behaviors
- Keymap
Displays
Configuration for keyboard display support, including widgets, screen type, threading, and LED blanking.
Kconfig
Core Display Settings
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_DISPLAY | bool | n | Enable display support |
CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE | bool | y (SSD1306) | Automatically blank the display when idle |
CONFIG_ZMK_DISPLAY_TICK_PERIOD_MS | int | 10 | Display task cycle interval in milliseconds |
CONFIG_ZMK_DISPLAY_INVERT | bool | n | Invert display colors (light-on-dark) |
Widgets
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_WIDGET_LAYER_STATUS | bool | y | Show the active keyboard layer |
CONFIG_ZMK_WIDGET_BATTERY_STATUS | bool | y | Show battery charge |
CONFIG_ZMK_WIDGET_BATTERY_STATUS_SHOW_PERCENTAGE | bool | n | Show battery as percentage instead of icon |
CONFIG_ZMK_WIDGET_OUTPUT_STATUS | bool | y | Show USB or Bluetooth connectivity status |
CONFIG_ZMK_WIDGET_WPM_STATUS | bool | n | Show words-per-minute typing speed |
Screen Type (select one)
| Option | Description |
|---|---|
CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN | Use the default built-in status screen |
CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM | Use a user-defined custom screen |
Work Queue / Threading (select one)
| Option | Description |
|---|---|
CONFIG_ZMK_DISPLAY_WORK_QUEUE_SYSTEM | Update display on the main thread |
CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED | Update display on a separate dedicated thread |
Dedicated Thread Parameters
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_STACK_SIZE | int | 2048 | Stack size for the dedicated display thread |
CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY | int | 5 | Priority of the dedicated display thread |
Devicetree
/chosen node
| Property | Type | Description |
|---|---|---|
zephyr,display | path | Specifies the active display device |
zmk,display-led | path | References an LED device (PWM or GPIO) for display blanking |
Supported Drivers
| Driver | Notes |
|---|---|
| IL0323 | ZMK-provided e-paper display driver |
| SSD1306 (I2C) | Zephyr ecosystem driver |
| SSD1306 (SPI) | Zephyr ecosystem driver |
Notes
CONFIG_ZMK_DISPLAY_INVERTmay not work as expected with custom status screens that use images.- Using
CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATEDprevents slow-updating displays from blocking keyboard responsiveness, at a memory overhead cost.
Related
- Power
- Battery
- LED Indicators
- Lighting
Encoders
Configuration for EC11 rotary encoders, including thread settings and per-sensor rotation triggers.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_EC11 | bool | n | Enable EC11 encoder support |
CONFIG_EC11_THREAD_PRIORITY | int | 10 | Encoder processing thread priority |
CONFIG_EC11_THREAD_STACK_SIZE | int | 1024 | Encoder processing thread stack size in bytes |
Trigger Mode (required when CONFIG_EC11=y, select one)
| Option | Description |
|---|---|
CONFIG_EC11_TRIGGER_NONE | Disable encoder interrupt/polling |
CONFIG_EC11_TRIGGER_GLOBAL_THREAD | Process encoder interrupts on the global thread |
CONFIG_EC11_TRIGGER_OWN_THREAD | Process encoder interrupts on a dedicated thread |
Devicetree
Keymap Sensor Configuration
| Property | Type | Description |
|---|---|---|
triggers-per-rotation | int | Number of times to trigger the bound behavior per full rotation |
Global settings apply to all sensors. Per-sensor overrides use ordered child nodes inside the zmk,keymap-sensors node; child node naming is arbitrary and order-based.
EC11 Node Properties
Compatible: alps,ec11
| Property | Type | Description |
|---|---|---|
a-gpios | GPIO array | Encoder A pin connection |
b-gpios | GPIO array | Encoder B pin connection |
steps | int | Number of encoder pulses per complete rotation |
Notes
- Per-sensor overrides in
zmk,keymap-sensorsapply in sequential order to the listed sensors; child node names are ignored.
Related
- Keymap
- Kscan
Keymap
Configuration for keyboard layer definitions and sensor bindings using Devicetree syntax.
Devicetree
Primary Node: zmk,keymap
Compatible: zmk,keymap
The keymap node itself has no properties. It contains child nodes representing keyboard layers, starting with the default layer (layer 0).
Layer Child Node Properties
| Property | Type | Description |
|---|---|---|
display-name | string | Layer name shown in ZMK Studio and on displays |
bindings | phandle-array | Key behavior references; order must match kscan configuration |
sensor-bindings | phandle-array | Sensor behavior references; order must match sensor definitions |
Example
/ {
keymap {
compatible = "zmk,keymap";
default_layer {
display-name = "Base";
bindings = <
&kp Q &kp W &kp E
>;
};
};
};Secondary Node: zmk,keymap-sensors
Compatible: zmk,keymap-sensors
| Property | Type | Description |
|---|---|---|
sensors | phandles | References to available sensor nodes |
Supported sensor types: EC11 encoders (alps,ec11).
Notes
- Binding order in
bindingsis critical and must match the keyboard scan configuration. sensor-bindingsorder must match the order of sensors declared inzmk,keymap-sensors.- Binding file:
zmk/app/dts/bindings/zmk,keymap.yaml
Related
- Behaviors
- Combos
- Encoders
- Kscan
- Layout
Kscan (Keyboard Scan)
Configuration for key detection drivers supporting direct GPIO, matrix, demux, charlieplex, composite, mock, and sideband behavior configurations.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE | int | 4 | Buffer size for pending scan events |
CONFIG_ZMK_KSCAN_INIT_PRIORITY | int | 40 | Driver initialization order priority |
CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS | int | -1 | Global press debounce override (ms); overrides per-device when set |
CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS | int | -1 | Global release debounce override (ms); overrides per-device when set |
Driver-Specific Kconfig
| Option | Type | Description |
|---|---|---|
CONFIG_ZMK_KSCAN_DIRECT_POLLING | bool | Use polling instead of interrupts for direct GPIO driver |
CONFIG_ZMK_KSCAN_MATRIX_POLLING | bool | Use polling instead of interrupts for matrix driver |
CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS | int | Matrix input settling time in ticks (default: 0) |
CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS | int | Matrix output settling delay in ticks (default: 0) |
CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTS | int | Charlieplex input settling time |
CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTS | int | Charlieplex output settling time |
Devicetree
/chosen node
| Property | Type | Description |
|---|---|---|
zmk,kscan | path | Active keyboard scan driver |
zmk,matrix-transform | path | Associated matrix transform node |
Direct GPIO Driver
| Property | Type | Default | Description |
|---|---|---|---|
input-gpios | GPIO array | required | One GPIO entry per key |
debounce-press-ms | int | 5 | Press debounce in milliseconds |
debounce-release-ms | int | 5 | Release debounce in milliseconds |
toggle-mode | bool | false | Minimize power drain for toggle switches |
wakeup-source | bool | false | Enable keyboard wake capability |
Matrix Driver
| Property | Type | Default | Description |
|---|---|---|---|
row-gpios | GPIO array | required | Row pin connections |
col-gpios | GPIO array | required | Column pin connections |
diode-direction | string | required | "row2col" or "col2row" |
debounce-press-ms | int | 5 | Press debounce in milliseconds |
debounce-release-ms | int | 5 | Release debounce in milliseconds |
wakeup-source | bool | false | Enable wake capability |
Demux Driver
| Property | Type | Default | Description |
|---|---|---|---|
input-gpios | GPIO array | required | GPIO inputs |
output-gpios | GPIO array | required | Demultiplexer address lines |
debounce-period | int | 5 | Debounce period in milliseconds |
polling-interval-msec | int | 25 | Polling interval in milliseconds |
Charlieplex Driver
| Property | Type | Description |
|---|---|---|
gpios | GPIO array | GPIOs used bidirectionally as input and output |
interrupt-gpios | GPIO array | Optional single GPIO for interrupt-based operation |
debounce-press-ms | int | Press debounce (default: 5ms) |
debounce-release-ms | int | Release debounce (default: 5ms) |
Composite Driver
| Property | Type | Description |
|---|---|---|
rows | int | Total composite matrix rows |
columns | int | Total composite matrix columns |
Child nodes specify individual drivers with optional row-offset and col-offset.
Mock Driver
| Property | Type | Description |
|---|---|---|
events | array | Array of simulated key events |
event-period | int | Milliseconds between generated events |
exit-after | bool | Terminate program after event sequence |
Kscan Sideband Behavior Driver
| Property | Type | Description |
|---|---|---|
kscan | phandle | Reference to the underlying scan driver |
auto-enable | bool | Unconditionally activate on startup |
Child nodes specify row, col, and bindings for each key to intercept.
Notes
- When global
CONFIG_ZMK_KSCAN_DEBOUNCE_*values are set, they override per-device debounce settings uniformly. - The demux driver does not honor
CONFIG_ZMK_KSCAN_DEBOUNCE_*global overrides. - GPIO flags differ between
row2colandcol2rowmatrix configurations.
Related
- Keymap
- Layout
- Encoders
Layout
Configuration for mapping the physical keyboard matrix to logical key positions, supporting matrix transforms, physical layouts, and multi-layout position mapping for ZMK Studio.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_PHYSICAL_LAYOUT_KEY_ROTATION | bool | y | Enable storage and support for key rotation information |
Devicetree
Matrix Transform
Compatible: zmk,matrix-transform
Maps logical keymap positions to physical keyboard scan (kscan) coordinates.
| Property | Type | Default | Description |
|---|---|---|---|
rows | int | required | Number of transformed matrix rows |
columns | int | required | Number of transformed matrix columns |
row-offset | int | 0 | Row offset for matrix lookups |
col-offset | int | 0 | Column offset for matrix lookups |
map | array | required | Position transforms using RC(row, col) macro |
The map array uses the RC(row, column) macro from dt-bindings/zmk/matrix_transform.h, with one entry per logical keymap position.
#include <dt-bindings/zmk/matrix_transform.h>
/ {
default_transform: keymap_transform_0 {
compatible = "zmk,matrix-transform";
columns = <3>;
rows = <2>;
map = <
RC(0,0) RC(0,1) RC(0,2)
RC(1,0) RC(1,1) RC(1,2)
>;
};
};Physical Layout
Compatible: zmk,physical-layout
Combines matrix transform, keyboard scan, and physical key attributes.
| Property | Type | Description |
|---|---|---|
display-name | string | Layout display name in ZMK Studio |
transform | phandle | Associated matrix transform |
kscan | phandle | Associated keyboard scan (falls back to /chosen zmk,kscan) |
keys | phandle-array | Physical key attributes |
Key Physical Attributes
Each key entry: <&key_physical_attrs width height x y rotation rotation_x rotation_y>
| Field | Unit | Description |
|---|---|---|
| width | centi-keyunit | Key cap width |
| height | centi-keyunit | Key cap height |
| x | centi-keyunit | Top-left X position |
| y | centi-keyunit | Top-left Y position |
| rotation | centi-degree | Rotation angle (positive = clockwise) |
| rotation_x | centi-keyunit | Rotation origin X |
| rotation_y | centi-keyunit | Rotation origin Y |
Physical Layout Position Map
Compatible: zmk,physical-layout-position-map
Preserves key mappings across different physical layouts when switching in ZMK Studio.
| Property | Type | Description |
|---|---|---|
complete | bool | Indicates mapping covers all keys; prevents position-based fallback |
physical-layout | phandle | References the corresponding physical layout |
positions | array | Key position array matching sibling position-map nodes |
Notes
key_physical_attrsmust be imported fromdts/physical_layouts.dtsi.- Multiple matrix transforms can be defined; select the active one via the
/chosennode. - Matrix
row-offset/col-offsetallow adjustment without modifying individualRC()entries. - Position maps enable seamless layout switching in ZMK Studio without remapping keys.
Related
- Kscan
- Keymap
- Studio
LED Indicators
Configuration for mapping HID indicator states (Caps Lock, Num Lock, etc.) to LED devices.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_INDICATOR_LEDS_INIT_PRIORITY | int | 91 | Indicator LED driver initialization priority |
The initialization priority must be greater than CONFIG_LED_INIT_PRIORITY.
Devicetree
Compatible: zmk,indicator-leds
Properties
| Property | Type | Default | Description |
|---|---|---|---|
indicator | int | required | The HID_INDICATOR_* value to indicate |
leds | phandles | required | One or more LED devices to control |
active-brightness | int | 100 | LED brightness (%) when the indicator is active |
inactive-brightness | int | 0 | LED brightness (%) when the indicator is not active |
disconnected-brightness | int | 0 | LED brightness (%) when the keyboard is not connected |
on-while-idle | bool | false | Keep LEDs enabled when the keyboard is idle on battery |
Notes on indicator Property
The indicator property must reference HID_INDICATOR_* definitions. Multiple indicator states can be combined with the pipe operator (|) to trigger the LED when any specified condition is active.
Notes
- Requires
CONFIG_ZMK_HID_INDICATORS=y(system setting) to receive LED state from the host.
Related
- System
- Displays
- Lighting
Lighting
Configuration for RGB underglow and backlight LED features. Changes made through lighting behaviors are saved to flash after a one-minute delay.
Kconfig
RGB Underglow
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_RGB_UNDERGLOW | bool | n | Enable RGB underglow |
CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER | bool | y | Control external power with underglow toggling |
CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE | bool | n | Disable underglow when idle |
CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB | bool | n | Disable underglow when USB disconnects |
CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP | int | 10 | Hue adjustment increment (0–359°) |
CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP | int | 10 | Saturation adjustment increment (%) |
CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP | int | 10 | Brightness adjustment increment (%) |
CONFIG_ZMK_RGB_UNDERGLOW_HUE_START | int | 0 | Initial hue value |
CONFIG_ZMK_RGB_UNDERGLOW_SAT_START | int | 100 | Initial saturation (%) |
CONFIG_ZMK_RGB_UNDERGLOW_BRT_START | int | 100 | Initial brightness (%) |
CONFIG_ZMK_RGB_UNDERGLOW_SPD_START | int | 3 | Initial effect speed (1–5) |
CONFIG_ZMK_RGB_UNDERGLOW_EFF_START | int | 0 | Initial effect: 0=solid, 1=breathe, 2=spectrum, 3=swirl |
CONFIG_ZMK_RGB_UNDERGLOW_ON_START | bool | y | Default power state on boot |
CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN | int | 0 | Minimum brightness limit (%) |
CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX | int | 100 | Maximum brightness limit (%) |
Backlight
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BACKLIGHT | bool | n | Enable LED backlight |
CONFIG_ZMK_BACKLIGHT_BRT_STEP | int | 20 | Brightness adjustment increment (%) |
CONFIG_ZMK_BACKLIGHT_BRT_START | int | 40 | Initial brightness (%) |
CONFIG_ZMK_BACKLIGHT_ON_START | bool | y | Default power state on boot |
CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE | bool | n | Disable backlight when idle |
CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB | bool | n | Disable backlight when USB disconnects |
Devicetree
RGB Underglow
ZMK uses Zephyr's LED strip driver bindings. No ZMK-specific Devicetree properties are required; configure the LED strip driver per Zephyr documentation.
Backlight (/chosen node)
| Property | Type | Description |
|---|---|---|
zmk,backlight | path | Reference to the backlight LED driver node (GPIO or PWM-based) |
Notes
- Lighting state (brightness, color, effect) persists to flash storage after a 1-minute debounce delay.
Related
- Power
- Settings
- LED Indicators
Configuration Overview
ZMK's configuration system allows compile-time customization through Kconfig and Devicetree files. All configuration is set at compile time; changes require building and flashing new firmware.
Configuration File Locations
| Location | Path Pattern | Purpose |
|---|---|---|
| User config | zmk-config/config/<name>.conf / <name>.keymap | Personal keyboard settings |
| Board folder | zmk/app/boards/<vendor>/<board>/ | Hardware specification |
| Shield folder | zmk/app/boards/shields/<shield>/ | Shield-specific settings |
For split keyboards, use shared filenames without _left / _right suffixes to configure both sides.
Kconfig Files
Text files containing CONFIG_XYZ=value assignments for global settings (keyboard name, hardware enablement).
| Value Type | Format | Example |
|---|---|---|
| bool | y or n | CONFIG_FOO=y |
| int | integer | CONFIG_FOO=42 |
| string | quoted text | CONFIG_FOO="text" |
Devicetree Files
| Extension | Role |
|---|---|
.dts | Base hardware definition |
.overlay | Adds/overrides .dts definitions |
.keymap | Keymap and user hardware configuration |
.dtsi | Include-only files |
Changing Properties
Use the ampersand reference syntax to modify existing nodes:
&kscan0 {
debounce-press-ms = <0>;
};Notes
- All configuration is compile-time only; re-flash to apply changes.
- Investigate the final combined Devicetree output during builds to diagnose issues.
Related
- Battery
- Behaviors
- Bluetooth
- Keymap
- Settings
Pointing
Configuration for pointing device (mouse) functionality, including input listeners, input processors, and split peripheral input routing.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_POINTING | bool | n | Enable general pointing/mouse functionality |
CONFIG_ZMK_POINTING_SMOOTH_SCROLLING | bool | n | Enable smooth scrolling via HID Resolution Multipliers |
CONFIG_INPUT_THREAD_STACK_SIZE | int | 512 (1024 on split peripherals) | Stack size for the dedicated input event processing thread |
Devicetree
Input Listener
Compatible: zmk,input-listener
Connects an input device to ZMK's input processing pipeline.
| Property | Type | Description |
|---|---|---|
device | phandle | Input device handle |
input-processors | phandle-array | List of input processors (with parameters) to apply to input events |
Layer-Specific Override Child Nodes
| Property | Type | Description |
|---|---|---|
layers | array | Layer indices where this override applies |
input-processors | phandle-array | Processors to apply when on specified layers |
process-next | bool | Continue processing with the parent config after this override |
Input Split
Compatible: zmk,input-split
Routes pointing device input from a split keyboard peripheral to the central.
| Property | Type | Description |
|---|---|---|
device | phandle | Input device handle on the peripheral |
input-processors | phandle-array | List of input processors to apply to input events |
Notes
CONFIG_ZMK_POINTING_SMOOTH_SCROLLINGrequires HID Resolution Multiplier support on the host OS.- Layer-specific overrides in
zmk,input-listenerchild nodes take precedence over the parentinput-processorsfor the specified layers.
Related
- Behaviors
- Split
- System
Power
Configuration for low power states (idle/sleep/soft-off), external power control, GPIO wakeup triggers, and soft-off wakeup sources.
Kconfig
Low Power States
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_IDLE_TIMEOUT | int | 30000 | Milliseconds of inactivity before entering idle state |
CONFIG_ZMK_SLEEP | bool | n | Enable deep sleep support |
CONFIG_ZMK_IDLE_SLEEP_TIMEOUT | int | 900000 | Milliseconds of inactivity before entering deep sleep |
CONFIG_ZMK_PM_SOFT_OFF | bool | n | Enable soft off from the keymap or dedicated hardware |
External Power Control
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_EXT_POWER | bool | y | Enable support to control external power output |
Devicetree
External Power Control
Compatible: zmk,ext-power-generic
| Property | Type | Description |
|---|---|---|
control-gpios | GPIO array | GPIOs to set active to enable external power |
init-delay-ms | int | Milliseconds to delay after driver initialization |
GPIO Key Wakeup Trigger
Compatible: zmk,gpio-key-wakeup-trigger
Enables GPIO-based wakeup from soft off state.
| Property | Type | Description |
|---|---|---|
trigger | phandle | GPIO key used to wake from soft off |
wakeup-source | bool | Mark this device as able to wake the keyboard |
extra-gpios | GPIO array | GPIO pins to set active before powering off (e.g., matrix column pins) |
Soft Off Wakeup Sources
Compatible: zmk,soft-off-wakeup-sources
| Property | Type | Description |
|---|---|---|
wakeup-sources | phandle array | Devices to enable during shutdown that can trigger wakeup |
Notes
wakeup-sourceshould always be present on thezmk,gpio-key-wakeup-triggernode.extra-gpioson the wakeup trigger should reference column pins for matrix keyboards to ensure keys are scannable during soft off.
Related
- Battery
- Displays
- Lighting
- Settings
Configuration
| Name | Description | Path |
|---|---|---|
| Overview | Configuration system overview: Kconfig and Devicetree file locations and value types | ./overview.md |
| Battery | Battery level reporting and sensor driver configuration | ./battery.md |
| Behaviors | Hold-tap, macro, sticky key, tap dance, caps word, and other behavior settings | ./behaviors.md |
| Bluetooth | BLE connectivity, pairing security, and device appearance settings | ./bluetooth.md |
| Bootloader | Double-tap reset, STM32 nBOOT_SEL, and magic value bootmode integration | ./bootloader.md |
| Combos | Multi-key simultaneous press combo timing and layer configuration | ./combos.md |
| Displays | Display enable, widgets, screen type, threading, and LED blanking | ./displays.md |
| Encoders | EC11 rotary encoder thread and per-sensor rotation trigger settings | ./encoders.md |
| Keymap | Layer definitions and sensor bindings via Devicetree | ./keymap.md |
| Kscan | Keyboard scan drivers: direct GPIO, matrix, demux, charlieplex, composite, mock, sideband | ./kscan.md |
| Layout | Matrix transform, physical layout, and multi-layout position mapping for ZMK Studio | ./layout.md |
| LED Indicators | HID indicator state (Caps Lock, Num Lock) to LED device mapping | ./led-indicators.md |
| Lighting | RGB underglow and backlight LED feature configuration | ./lighting.md |
| Pointing | Pointing device/mouse input listener, processors, and split peripheral routing | ./pointing.md |
| Power | Idle/sleep/soft-off timeouts, external power control, and GPIO wakeup triggers | ./power.md |
| Settings | Persistent flash storage settings: save debounce and startup reset | ./settings.md |
| Split | Split keyboard BLE and wired (UART) communication settings | ./split.md |
| Studio | ZMK Studio locking, layer name limits, and BLE transport tuning | ./studio.md |
| System | Keyboard name, HID roll-over, USB descriptors, BLE stack, and logging | ./system.md |
Settings
Configuration for persistent runtime settings stored in the controller's flash memory, including save debounce and reset behavior.
Kconfig
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_SETTINGS_RESET_ON_START | bool | n | Clear all persistent settings at startup |
CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE | int | 60000 | Milliseconds to wait before writing setting changes to flash |
Features Using Persistent Storage
The following ZMK capabilities store data in flash and persist across power cycles and firmware updates:
| Feature | What Is Stored |
|---|---|
| Bluetooth | Pairing keys, MAC addresses, selected profile |
| Split keyboards | Wireless pairing data between halves |
| Output selection | Last preferred endpoint (USB vs. BT) |
| ZMK Studio | Runtime keymap modifications and layout selections |
| Lighting | Brightness, color, and effect settings |
| Power management | External power toggle state |
Notes
- Regular ZMK firmware flashes intentionally preserve persistent settings to avoid losing Bluetooth pairings when keymaps are updated.
- To reset all settings: build with the
settings_resetshield, flash it, then reflash normal firmware. - For split keyboards, clear settings on both halves before re-pairing.
- ZMK Studio users can use "Restore Stock Settings" in the client interface as an alternative reset method.
- The save debounce (
CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE) reduces flash wear by batching writes.
Related
- Bluetooth
- Lighting
- Power
- Split
- Studio
Split
Configuration for split keyboard behavior, supporting both Bluetooth and wired (UART) connections between keyboard halves.
Kconfig
Core Split Settings
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_SPLIT | bool | n | Enable split keyboard functionality |
CONFIG_ZMK_SPLIT_ROLE_CENTRAL | bool | n | Designate device as central (y) or peripheral (n) |
CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS | bool | n | Transmit HID indicator state to peripherals |
Bluetooth Split
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_SPLIT_BLE | bool | y | Use BLE for inter-half communication |
CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS | int | 1 | Number of connectable peripherals |
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING | bool | n | Retrieve battery data from peripherals |
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY | bool | n | Report peripheral battery levels to hosts |
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE | int | 5 | Key state event queue capacity |
CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE | int | 756 | Peripheral thread stack allocation in bytes |
Wired Split
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_SPLIT_WIRED | bool | — | Enable wired connections (conditional on devicetree) |
CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC | bool | — | DMA-based async UART mode (SAM0, STM32) |
CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT | bool | — | Interrupt-driven UART mode (RP2040, nRF52) |
CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING | bool | — | Polling UART mode (universal fallback) |
CONFIG_ZMK_SPLIT_WIRED_ASYNC_RX_TIMEOUT | int | 20 | Microseconds before reporting received data (async mode) |
CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD | int | 10 | Polling interval in ticks (polling mode) |
Devicetree
Wired Split
Compatible: zmk,wired-split
| Property | Type | Description |
|---|---|---|
device | phandle | Reference to the UART node |
/ {
wired_split {
compatible = "zmk,wired-split";
device = <&pro_micro_serial>;
};
};Notes
CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALSand BT connection/pairing counts (CONFIG_BT_MAX_CONN,CONFIG_BT_MAX_PAIRED) must be set one greater than the desired number of Bluetooth profiles on the central side.- For BLE peripheral battery reporting, enable both
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHINGandCONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY. - Async/DMA mode offers best performance; interrupt mode is suitable for RP2040/nRF52; polling mode is the universal fallback.
Related
- Bluetooth
- Battery
- Settings
- System
Studio
Configuration for ZMK Studio, including keymap layer name limits, session locking, and BLE transport tuning.
Kconfig
Keymaps
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_KEYMAP_LAYER_NAME_MAX_LEN | int | 20 | Max allowable keymap layer display name length in characters |
Locking
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_STUDIO_LOCKING | bool | y | Enable/disable locking for ZMK Studio |
CONFIG_ZMK_STUDIO_LOCK_IDLE_TIMEOUT_SEC | int | 500 | Seconds of inactivity before automatically locking ZMK Studio |
CONFIG_ZMK_STUDIO_LOCK_ON_DISCONNECT | bool | y | Automatically lock when ZMK Studio disconnects |
Transport / RPC
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY | int | 10 | Lower BLE connection latency to request while ZMK Studio is active |
CONFIG_ZMK_STUDIO_RPC_THREAD_STACK_SIZE | int | 1800 | Stack size for the dedicated RPC thread |
CONFIG_ZMK_STUDIO_RPC_RX_BUF_SIZE | int | 30 | Bytes available for buffering incoming RPC messages |
CONFIG_ZMK_STUDIO_RPC_TX_BUF_SIZE | int | 64 | Bytes available for buffering outgoing RPC messages |
Notes
- ZMK Studio stores runtime keymap modifications and layout selections in persistent flash storage.
- Users can reset Studio changes via the "Restore Stock Settings" button in the ZMK Studio client interface.
- No Devicetree properties are defined for this feature.
Related
- Settings
- Keymap
- Layout
- Bluetooth
System
General system settings controlling keyboard identity, HID report types, USB descriptors, Bluetooth stack, and logging.
Kconfig
General
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_BOARD_COMPAT | bool | n | Validate proper ZMK board configuration |
CONFIG_ZMK_KEYBOARD_NAME | string | — | Keyboard device name (max 16 characters) |
CONFIG_ZMK_WPM | bool | n | Enable words-per-minute calculation |
CONFIG_HEAP_MEM_POOL_SIZE | int | 8192 | Heap memory pool size in bytes |
HID Configuration
| Option | Type | Description |
|---|---|---|
CONFIG_ZMK_HID_REPORT_TYPE_HKRO | bool | Standard 6-key roll-over (default; mutually exclusive with NKRO) |
CONFIG_ZMK_HID_REPORT_TYPE_NKRO | bool | Full N-key roll-over (may cause BIOS incompatibility) |
CONFIG_ZMK_HID_INDICATORS | bool | Receive LED indicator state from hosts |
CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE | int | Concurrent consumer keys (default: 6) |
CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE | int | Concurrent keyboard keys in HKRO mode (default: 6) |
USB
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_USB | bool | enabled | Core USB support |
CONFIG_USB_DEVICE_VID | int | 0x1D50 | USB vendor ID |
CONFIG_USB_DEVICE_PID | int | 0x615E | USB product ID |
CONFIG_USB_DEVICE_MANUFACTURER | string | "ZMK Project" | USB manufacturer string |
CONFIG_ZMK_USB_BOOT | bool | n | Enable USB boot protocol support |
Bluetooth
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_BT | bool | enabled | Core Bluetooth support |
CONFIG_BT_MAX_CONN | int | 5 | Maximum concurrent BLE connections |
CONFIG_BT_MAX_PAIRED | int | 5 | Maximum number of paired devices |
CONFIG_ZMK_BLE | bool | enabled | ZMK BLE keyboard mode |
CONFIG_ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE | int | 5 | Consumer HID report queue depth |
CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE | int | 20 | Keyboard HID report queue depth |
Logging
| Option | Type | Default | Description |
|---|---|---|---|
CONFIG_ZMK_USB_LOGGING | bool | n | Enable USB CDC ACM debug output |
CONFIG_ZMK_LOG_LEVEL | int | 4 | Log verbosity level |
Build Snippets (nRF boards)
| Snippet | Description |
|---|---|
nrf52833-nosd | Extends code partition from 280 KB to 428 KB by removing Nordic SoftDevice |
nrf52840-nosd | Extends code/storage to 844 KB/128 KB by removing SoftDevice |
Notes
- Changing
CONFIG_ZMK_KEYBOARD_NAMErequires clearing stored controller settings for the new name to take effect over BLE. - On split keyboards,
CONFIG_BT_MAX_CONNandCONFIG_BT_MAX_PAIREDmust be set to one greater than the desired number of Bluetooth profiles, on the central only. - The
nrf52*-nosdsnippets erase the SoftDevice. Flashing incompatible firmware afterward can permanently brick the board; bootloader re-flashing requires specialized recovery.
Related
- Bluetooth
- Split
- Settings
- Bootloader
Clean Room Implementation
ZMK is a clean room design firmware inspired by QMK/TMK, rewritten entirely under MIT license without copying any GPL-licensed code.
Notes
- All contributors MUST read and follow this policy before submitting code.
- Do NOT reuse, copy, or reference code from GPL-licensed projects (e.g., QMK, TMK).
- You MAY read documentation from GPL projects to understand feature behavior for reimplementation.
- You MAY inspect existing layouts/keymaps via the QMK Configurator and recreate them for ZMK — but never copy source code.
- Violating this policy would compromise ZMK's MIT license.
Related
- Clean Room Design (Wikipedia)
- QMK Configurator
- Pull Requests
- Documentation
Documentation
Guide for contributing documentation changes to ZMK, which uses Docusaurus as its documentation framework.
Signature / Usage
# From the docs/ folder
npm ci # Install dependencies (first time or after source updates)
npm start # Launch local dev server at http://localhost:3000
# Before submitting
npm run prettier:format # Auto-format
npm run prettier:check # Verify formatting
npm run lint # Run linter
npm run build # Full build check — all must passNotes
- Read the Clean Room Policy before contributing.
- Node.js/npm is required; already included in the ZMK dev container.
- VS Code + Docker users: the local server requires a manual restart after saving changes; auto-reload is not available in that environment.
- Use American English spelling and grammar.
- Heading case: Title Case for the first three heading levels; sentence case for all levels below.
- Prettier and lint cannot enforce language conventions — manual review is required.
- All checks (
prettier:check,lint,build) must pass before opening a PR.
Related
- Clean Room Policy
- Pull Requests
Pull Requests
All changes to ZMK's main branch — including those from core committers — require a pull request.
Commit Message Format
<type>[optional scope]: <short summary>
<body — paragraph or bulleted list>
[optional git trailers]Types
| Type | Purpose |
|---|---|
feat | New feature |
fix | Bug fix |
refactor | Code restructuring without behavior change |
docs | Documentation update |
blog | Documentation blog post |
ci | GitHub Actions workflow changes |
chore | Miscellaneous small changes |
feat! / fix! / refactor! | Breaking change |
Common Scopes
hid, usb, ble, power, split, studio, display, underglow, backlight, behaviors, core, boards, shields
Notes
- Follow Conventional Commits format for all commit messages.
- Keep commits discrete and cohesive; use
git commit --amendor interactive rebase to combine related changes. - Do not use merge commits — rebase against
upstream/mainto maintain linear history. - Fill out the auto-populated PR template completely when opening a PR on GitHub.
- Reviewers assess based on area of expertise; maintainers verify CI, inspect commit quality, and use squash-merge.
Related
- Clean Room Policy
- Documentation
- ZMK Pull Requests on GitHub
Contributing
| Name | Description | Path |
|---|---|---|
| Clean Room Implementation | License policy — what contributors may and may not reference from GPL projects | ./clean-room.md |
| Documentation | How to run, format, and submit documentation changes using Docusaurus | ./documentation.md |
| Pull Requests | Commit message conventions, branch hygiene, and PR submission/review process | ./pull-requests.md |
Devicetree Overview
ZMK uses devicetree as a declarative way to describe hardware, keymaps, behaviors, and board configuration. It is the primary mechanism for both defining and customizing ZMK firmware.
Signature / Usage
Basic node syntax:
[label:] name {
compatible = "zmk,behavior-mod-morph";
#binding-cells = <0>;
bindings = <&kp SPACE>, <&kp UNDERSCORE>;
mods = <(MOD_LSFT|MOD_RSFT)>;
};Overriding an existing node by phandle:
&existing_node {
property = <2>;
};Deleting nodes or properties:
/delete-node/ &node_label;
/delete-property/ property-name;Options / Props
| Property | Type | Description |
|---|---|---|
compatible | string | Maps a node to its driver/binding; identifies valid properties |
status | string | "okay" (enabled) or "disabled" |
#binding-cells | int | Number of parameters a phandle binding accepts |
Property value types:
| Type | Syntax Example | Description |
|---|---|---|
| bool | (property present) | True when present, false when absent |
| int | <42> | Single integer in angle brackets |
| string | "value" | Text in double quotes |
| array | <1 2 3> | Space-separated integers in angle brackets |
| phandle | <&label> | Reference to another node |
| phandle-array | <&label param> | Reference with associated parameters |
Notes
- File types:
.dtsi(shared includes),.dts(board base),.overlay(board modifications),.keymap(ZMK-specific final overlay) - Preprocessing happens in two passes: the C preprocessor and devicetree's own merge system. Later files overwrite earlier properties.
- Binding files in
app/dts/bindings/are authoritative for valid properties percompatiblevalue. - Phandle-based node overrides must not place the override block inside the root node
/. - Use
/omit-if-no-ref/ &node_label;to drop unreferenced nodes from the build output.
Related
- new-behavior
- ZMK Bindings Directory
- Zephyr Devicetree Guide
ZMK Events
ZMK's event manager decouples behaviors and peripherals from core firmware by providing a pub/sub system for typed events. Modules subscribe to specific event types and receive callbacks when those events are raised.
Signature / Usage
Declare an event type (header file):
struct zmk_endpoint_changed {
struct zmk_endpoint_instance endpoint;
};
ZMK_EVENT_DECLARE(zmk_endpoint_changed);Register a listener and subscription:
ZMK_LISTENER(combo, behavior_combo_listener);
ZMK_SUBSCRIPTION(combo, zmk_keycode_state_changed);Listener callback:
int behavior_hold_tap_listener(const zmk_event_t *eh) {
if (as_zmk_position_state_changed(eh) != NULL) {
return my_position_state_handler(eh);
}
return ZMK_EV_EVENT_BUBBLE;
}Raising and freeing events:
raise_zmk_specific_thing_happened(struct zmk_specific_thing_happened event);
ZMK_EVENT_RAISE(ev);
ZMK_EVENT_FREE(ev);Options / Props
Listener return values:
| Value | Description |
|---|---|
ZMK_EV_EVENT_BUBBLE | Continue propagating to next listener |
ZMK_EV_EVENT_HANDLED | Stop propagation; event manager frees memory |
ZMK_EV_EVENT_CAPTURED | Stop propagation; caller owns and must free memory |
Common built-in event types (from `app/include/zmk/events/`):
| Header | Event |
|---|---|
hid_indicators_changed.h | Num/Caps/Scroll Lock state |
keycode_state_changed.h | Keycode press/release with HID details |
layer_state_changed.h | Layer activation/deactivation |
position_state_changed.h | Key position press/release |
Notes
- External module listeners have the highest priority because they are linked first.
- Listeners should return
ZMK_EV_EVENT_BUBBLEwhenever possible; only capture events when strictly necessary. - Listener priority within ZMK is determined by the order of source files in
CMakeLists.txt. - Priority between external modules is determined by
west.ymlproject order—avoid creating order-dependent designs. as_<event_type>(eh)returnsNULLif the event does not match the type, enabling safe type checks.
Related
- new-behavior
- module-creation
- ZMK Event Header Files
Module Creation
ZMK modules are the recommended way to extend ZMK firmware with new keyboards, behaviors, drivers, or features without modifying the ZMK core repository.
Signature / Usage
`zephyr/module.yml` — minimum required file:
name: <your-module-name>`west.yml` — dependency manifest:
manifest:
remotes:
- name: remote-name
url-base: https://github.com/remote-name
projects:
- name: repository-name
remote: remote-name
import: west.ymlOptions / Props
Module naming convention: zmk-<type>-<description>
| Type | Description |
|---|---|
keyboard | Board or shield definitions |
component | Component definitions |
behavior | A single custom behavior |
driver | A single custom driver |
feature | Other firmware features |
vfx | Visual effects |
`zephyr/module.yml` build properties:
| Property | Description |
|---|---|
depends | Other modules this module depends on |
cmake | CMake configuration options |
kconfig | Kconfig file paths |
settings.board_root | Path to board definitions |
settings.dts_root | Path to devicetree source files |
settings.snippet_root | Path to build snippets |
Notes
- Start from the official ZMK Module Template repository.
- The module
nameinzephyr/module.ymlmay differ from the GitHub repository name. - Place public header files under
include/zmk_<type>_<description>/so paths are unambiguous. - Every module must include a
README.mdand aLICENSEfile.
Related
- new-behavior
- events
- ZMK Module Template
- Zephyr Module Documentation
New Behavior
Step-by-step guide for creating a custom ZMK behavior—the action invoked when a key is pressed or released. Behaviors are implemented as Zephyr RTOS devices and distributed as ZMK modules.
Signature / Usage
Minimal behavior source file (`src/behaviors/behavior_<name>.c`):
#define DT_DRV_COMPAT zmk_behavior_<name>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>
#include <drivers/behavior.h>
#include <zmk/behavior.h>
static int on_<name>_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return ZMK_BEHAVIOR_OPAQUE;
}
static int on_<name>_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return ZMK_BEHAVIOR_OPAQUE;
}
static const struct behavior_driver_api <name>_driver_api = {
.binding_pressed = on_<name>_binding_pressed,
.binding_released = on_<name>_binding_released,
};
BEHAVIOR_DT_INST_DEFINE(0, <name>_init, NULL, NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&<name>_driver_api);Devicetree binding (`dts/bindings/behaviors/zmk,behavior-<name>.yaml`):
description: My custom behavior
compatible: "zmk,behavior-<name>"
include: zero_param.yaml # or one_param.yaml / two_param.yaml
properties:
my-option:
type: int
default: 0Kconfig (`Kconfig`):
config ZMK_BEHAVIOR_<NAME>
bool
default y
depends on DT_HAS_ZMK_BEHAVIOR_<NAME>_ENABLEDCMakeLists.txt (split-keyboard safe):
if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_<name>.c)
endif()Options / Props
Repository structure:
zmk-behavior-<name>/
├── CMakeLists.txt
├── Kconfig
├── LICENSE
├── README.md
├── dts/bindings/behaviors/zmk,behavior-<name>.yaml
├── dts/behaviors/<name>.dtsi # optional predefined instances
├── include/dt-bindings/zmk/<name>.h # optional HID/constant headers
├── src/behaviors/behavior_<name>.c
├── tests/<name>/
└── zephyr/module.ymlBehavior locality (`.locality` field in `behavior_driver_api`):
| Value | Effect |
|---|---|
BEHAVIOR_LOCALITY_CENTRAL | Runs on central half only (default for most behaviors) |
BEHAVIOR_LOCALITY_EVENT_SOURCE | Runs on the half that invoked the behavior |
BEHAVIOR_LOCALITY_GLOBAL | Runs on all halves (e.g., RGB lighting) |
Useful ZMK API functions:
| Function / Macro | Description |
|---|---|
zmk_behavior_get_binding(name) | Retrieve a behavior device by name |
zmk_behavior_invoke_binding(...) | Invoke another behavior from within a behavior |
raise_zmk_keycode_state_changed_from_encoded(param, pressed, ts) | Emit a keycode HID event |
zmk_keymap_layer_activate(layer) | Activate a keymap layer |
zmk_keymap_layer_deactivate(layer) | Deactivate a keymap layer |
zmk_keymap_layer_toggle(layer) | Toggle a keymap layer |
zmk_keymap_layer_to(layer) | Switch to a specific layer |
Notes
- Read the ZMK clean room policy before starting development.
- Behaviors must access system state via the event manager (
#include <zmk/event_manager.h>); see events. - Predefined behavior instances in
.dtsifiles: node names must be ≤ 8 characters if locality is notBEHAVIOR_LOCALITY_CENTRAL, so the node is addressable from split peripheral halves. - Use USB logging for hardware testing since Zephyr does not support logging over Bluetooth.
- Run
west testfrom the module root to execute virtual test suites. - C source files submitted to ZMK must be formatted with
clang-format. - Code from QMK/TMK may not be used as source material for ZMK due to GPL license restrictions.
- Use MIT SPDX headers for ZMK contributions:
SPDX-License-Identifier: MIT.
Related
- module-creation
- devicetree
- events
- usb-logging
- ZMK Module Template
Development
| Name | Description | Path |
|---|---|---|
| Devicetree Overview | Declarative hardware/keymap description syntax used throughout ZMK | ./devicetree.md |
| ZMK Events | Pub/sub event manager for decoupling behaviors and peripherals | ./events.md |
| Module Creation | How to create and publish a ZMK module (keyboard, behavior, driver, feature) | ./module-creation.md |
| New Behavior | End-to-end guide for implementing a custom key behavior as a ZMK module | ./new-behavior.md |
| Studio RPC Protocol | Protocol Buffer + byte-framing RPC protocol used by ZMK Studio UI | ./studio-rpc-protocol.md |
| USB Logging | Redirect firmware log output to USB CDC ACM for debugging | ./usb-logging.md |
Studio RPC Protocol
The ZMK Studio UI communicates with ZMK devices over a custom RPC protocol built on Protocol Buffers with a simple byte-framing scheme, transmitted over USB serial or BLE.
Signature / Usage
Message framing control bytes:
| Byte | Value | Role |
|---|---|---|
| SoF (Start of Frame) | 0xAB | Marks the beginning of a message |
| Esc (Escape) | 0xAC | Escapes a control byte appearing in payload data |
| EoF (End of Frame) | 0xAD | Marks the end of a message |
When a payload byte matches a control byte value, it is escaped by prefixing it with 0xAC.
Message flow:
Client → Device : Request (protobuf-encoded, framed)
Device → Client : Response → RequestResponse (reply to a request)
Device → Client : Response → Notification (unsolicited state update)Options / Props
BLE Transport (GATT):
| Attribute | Value |
|---|---|
| Service UUID | 00000000-0196-6107-c967-c5cfb1c2482a |
| Characteristic UUID | 00000001-0196-6107-c967-c5cfb1c2482a |
| Device-to-client direction | GATT Indications |
USB Transport:
- USB class: CDC/ACM (serial port over USB)
- Framed messages are sent over the CDC/ACM UART interface.
Notes
- Protocol Buffer message definitions live in the zmk-studio-messages repository.
- Review the ZMK clean room policy before contributing to protocol-related code.
Related
- usb-logging
- zmk-studio-messages
USB Logging
USB logging redirects ZMK's printk, console output, and Zephyr log messages to a USB CDC ACM serial device, enabling debugging on keyboards that have no UART interface.
Signature / Usage
GitHub Actions `build.yaml` (add snippet to a shield build):
---
include:
- board: nice_nano
shield: corne_left
snippet: zmk-usb-loggingLocal build with `west`:
west build -b nice_nano -S zmk-usb-logging -- -DSHIELD="corne_left"Connect on Linux:
sudo tio /dev/ttyACM0Connect on macOS:
sudo tio /dev/tty.usbmodem14401Notes
- USB logging increases power consumption and can noticeably reduce battery life; disable it when not debugging.
- Standard boards such as the nice!nano and Seeed Studio XIAO are pre-configured; no extra board-level changes are needed.
- Custom boards must expose the USB device node with:
zephyr_udc0: &usbd { status = "okay"; } - Legacy configurations may use
CONFIG_ZMK_USB_LOGGING=yin a Kconfig file instead of the snippet. - Bluetooth logging is not supported by Zephyr; USB logging is the only practical on-device log output method.
Related
Battery Level
Reports battery level to connected Bluetooth hosts and optionally to the keyboard's display. Boards using ZMK's officially supported hardware have battery sensing pre-configured.
Notes
- Only the central side's battery is reported over Bluetooth by default; peripheral battery monitoring requires additional setup and host support.
- Windows may not properly request battery updates, which can result in stale readings.
- Custom boards must follow the hardware integration guide to implement battery sensing.
Related
- config/battery
- split-keyboards
Bluetooth
Enables wireless keyboard connectivity via Bluetooth Low Energy (BLE), supporting both host connections and split keyboard communication. Requires Bluetooth 4.2 or newer on the host.
Notes
- ZMK supports up to 5 bonded device profiles by default; profiles must be selected manually when pairing new devices.
- Multiple hosts may show as "connected" simultaneously, but only the active profile receives keystrokes.
- Security uses Elliptic Curve Diffie Hellman (ECDH) for key generation. Numeric Comparison association model (for stronger pairing) is experimental.
- After enabling features that modify the HID descriptor (e.g., mouse keys, NKRO), remove and re-pair the device on the host to refresh the cached descriptor.
- Mismatched bond keys between profiles can cause unexpected behavior.
Related
- behaviors/bluetooth
- split-keyboards
- troubleshooting/connection-issues
Debouncing
ZMK uses a cycle-based debounce algorithm where each key is debounced independently. By default a key must remain stable for 5 ms before a state change is registered.
Signature / Usage
# .conf — global overrides
CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=5
CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS=5/* per-driver devicetree override */
&kscan0 {
debounce-press-ms = <3>;
debounce-release-ms = <3>;
};Options / Props
| Name | Type | Description |
|---|---|---|
CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS | int (0–16383) | Global press debounce time in ms (default: 5) |
CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS | int (0–16383) | Global release debounce time in ms (default: 5) |
debounce-press-ms | int | Per-driver press debounce time in ms |
debounce-release-ms | int | Per-driver release debounce time in ms |
debounce-scan-period-ms | int | Scan frequency while debouncing (default: 1ms) |
Notes
- Supported by
zmk,kscan-gpio-matrixandzmk,kscan-gpio-directdrivers only. - Global Kconfig options override per-driver devicetree settings.
- Timers round up to the next multiple of
debounce-scan-period-ms. - For minimum latency, set
DEBOUNCE_PRESS_MS=0or1with a higher release value. debounce-perioddevicetree property is deprecated.- Equivalent to QMK's
sym_defer_pkalgorithm; eager mode mirrorsasym_eager_defer_pk.
Displays
Proof-of-concept support for OLED and ePaper displays on ZMK-powered keyboards. Not yet production-ready; full documentation is pending.
Notes
- Current status: proof of concept — not recommended for production use.
- Known bug: display remains blank after resuming from an external power cutoff (tracked in zmkfirmware/zmk#674).
- No Kconfig symbols or devicetree properties are publicly documented for this feature yet.
Related
- lighting
- pointing
Encoders
Support for EC11 rotary encoders with push buttons. Push button and rotation behaviors are configured independently.
Signature / Usage
# .conf
CONFIG_EC11=y
CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y/* keymap — rotation bindings */
sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>;Options / Props
| Name | Description |
|---|---|
CONFIG_EC11 | Enables EC11 encoder driver |
CONFIG_EC11_TRIGGER_GLOBAL_THREAD | Uses global thread for encoder event triggering |
sensor-bindings | Keymap property; list of <BINDING [CW_KEY] [CCW_KEY]> per encoder |
Notes
- The encoder push button connects to the keyboard matrix and is mapped like any standard key.
sensor-bindingsaccepts one entry per encoder;&inc_dec_kpis the built-in behavior for sending keycodes on rotation.- After editing
.conf, push and flash the updated firmware to activate the changes.
Related
- behaviors/sensor-rotate
- development/hardware-integration/encoders
LED Indicators
Displays the five standard HID indicator states (Num Lock, Caps Lock, Scroll Lock, Compose, Kana) via LEDs when the hardware supports them.
Signature / Usage
&caps_lock_indicator {
active-brightness = <0>;
inactive-brightness = <100>;
disconnected-brightness = <50>;
};Options / Props
| Property | Description |
|---|---|
active-brightness | Brightness when indicator state is active |
inactive-brightness | Brightness when indicator state is inactive |
disconnected-brightness | Brightness when not connected to a host |
on-while-idle | Prevents the LED from turning off during idle on battery power |
Available node labels: num_lock_indicator, caps_lock_indicator, scroll_lock_indicator, compose_indicator, kana_indicator.
Notes
- Default behavior: off when idle on battery, off when disconnected, on at full brightness when active.
- Brightness values greater than 0 default to maximum on LEDs that lack PWM support.
- PWM brightness control is available but slightly increases power consumption.
Related
- development/hardware-integration/lighting/led-indicators
Lighting
ZMK supports two independent lighting systems: RGB Underglow (addressable RGB LEDs) and Backlight (single-color parallel LEDs). The choice between them depends on the LED driver, not the physical LED position.
Signature / Usage
# RGB Underglow
CONFIG_ZMK_RGB_UNDERGLOW=y
# Backlight
CONFIG_ZMK_BACKLIGHT=y/* Set number of LEDs in the RGB strip */
&led_strip {
chain-length = <21>;
};Options / Props
| Name | Description |
|---|---|
CONFIG_ZMK_RGB_UNDERGLOW | Enables RGB underglow support |
CONFIG_ZMK_BACKLIGHT | Enables single-color backlight support |
chain-length | Devicetree property — number of LEDs in the RGB strip |
Notes
- Supported RGB LED families: WS2812, APA102, LPD880x.
- For split keyboards, set
chain-lengthto the LED count per half, not the total. - Backlight cannot control individual LED brightness; all LEDs share the same power level.
- Full configuration options are documented at
/docs/config/lighting.
Related
- config/lighting
- led-indicators
Low Power States
ZMK provides three low-power modes to extend battery life: Idle, Deep Sleep, and Soft Off.
Options / Props
| State | Description | Default Timeout |
|---|---|---|
| Idle | Disables displays and lighting; Bluetooth stays connected | 30 seconds |
| Deep Sleep | Full software power-off; disconnects Bluetooth, clears RAM; wakeup takes several seconds | Must be enabled explicitly |
| Soft Off | Explicit on/off via button or keymap binding; comparable savings to deep sleep | N/A (manual trigger) |
Notes
- Deep Sleep must be explicitly enabled in power configuration settings.
kscannodes require thewakeup-sourcedevicetree property to serve as wake sources; this is recommended even when deep sleep is not used.- Deep Sleep clears any unsaved ZMK Studio changes.
- Soft Off is more restrictive in wake sources than Deep Sleep (typically only a designated GPIO pin or the reset button).
- For split keyboards, the reset button must be pressed on each half to wake from Soft Off.
Related
- behaviors/soft-off
- development/hardware-integration/soft-off-setup
- config/power
Modules
ZMK leverages Zephyr modules to incorporate external source code or configuration into builds — similar to plugins or themes. Common uses include adding support for keyboards outside the main ZMK repo and adding custom drivers or behaviors.
Signature / Usage
# zmk-config/config/west.yml — adding an external module
manifest:
remotes:
- name: zmkfirmware
url-base: https://github.com/zmkfirmware
- name: my-module-owner
url-base: https://github.com/my-module-owner
projects:
- name: zmk
remote: zmkfirmware
revision: main
import: app/west.yml
- name: my-zmk-module
remote: my-module-owner
revision: main
self:
path: configNotes
- A typical ZMK build involves three components: the user ZMK config, ZMK modules, and the core ZMK firmware.
- For GitHub Actions builds, edit
west.ymlto add remotes and project entries; use theimportproperty for modules that have their own dependencies. - For local builds, clone the module into your local file tree and pass it as an external module during
west build. - Unreleased features from open pull requests can be tested by pointing
west.ymlto the developer's fork and branch instead of the official repository.
Related
- development/module-creation
- Zephyr West Manifests
Pointing Devices
Supports physical pointing devices (trackpads, trackballs) and mouse emulation behaviors for sending HID pointing events to connected hosts.
Signature / Usage
# .conf
CONFIG_ZMK_POINTING=y/* keymap — mouse emulation behavior */
&mmv MOVE_UPNotes
- Enabling this feature modifies the HID report descriptor. Remove and re-pair Bluetooth hosts after enabling to ensure they pick up the updated descriptor.
- Two implementation paths are available:
- Mouse emulation: use keymap behaviors (e.g.,
&mmv) to send mouse events without dedicated hardware. - Physical pointing devices: integrate actual hardware via the pointer hardware integration guide.
- Input Processors can modify pointing behavior (scaling, scroll mode conversion, temporary layer activation).
- Input Listeners connect low-level input devices to ZMK's HID system and apply processors.
Related
- config/pointing
- behaviors/mouse-emulation
- bluetooth
Features
| Name | Description | Path |
|---|---|---|
| Battery Level | Reports battery level to Bluetooth hosts and displays | ./battery.md |
| Bluetooth | BLE wireless connectivity, profile management, and security | ./bluetooth.md |
| Debouncing | Cycle-based per-key debounce algorithm with configurable press/release timings | ./debouncing.md |
| Displays | Proof-of-concept OLED/ePaper display support (not production-ready) | ./displays.md |
| Encoders | EC11 rotary encoder support with configurable rotation and push-button bindings | ./encoders.md |
| LED Indicators | HID-standard Num/Caps/Scroll Lock LED indicators with brightness control | ./led-indicators.md |
| Lighting | RGB Underglow (addressable LEDs) and single-color Backlight support | ./lighting.md |
| Low Power States | Idle, Deep Sleep, and Soft Off modes for battery optimization | ./low-power-states.md |
| Modules | Zephyr module integration for external keyboards, drivers, and behaviors | ./modules.md |
| Pointing Devices | Physical pointing device support and mouse emulation via HID | ./pointing.md |
| Split Keyboards | Multi-part split keyboard support over BLE or wired UART | ./split-keyboards.md |
| ZMK Studio | Runtime keymap editing without reflashing via USB or Bluetooth | ./studio.md |
Split Keyboards
Enables keyboards split into multiple physical parts with independent controllers. One part acts as the "central" (manages keymap logic and host communication); others are "peripherals" that forward key and sensor events to the central.
Signature / Usage
# Central .conf
CONFIG_ZMK_SPLIT=y
CONFIG_ZMK_SPLIT_ROLE_CENTRAL=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS=1/* Wired (full-duplex UART) transport */
compatible = "zmk,wired-split";Options / Props
| Symbol | Description |
|---|---|
CONFIG_ZMK_SPLIT | Enables split keyboard functionality |
CONFIG_ZMK_SPLIT_ROLE_CENTRAL | Designates this firmware as the central role |
CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS | Number of BLE peripherals to connect |
Notes
- BLE transport increases average latency by ~3.75 ms (worst case ~7.5 ms).
- Wired UART transport supports only a single peripheral and is experimental.
- Safety: never insert or remove the TRRS/UART cable while a controller is powered by USB or battery — this can permanently damage controllers.
- Split keyboards require separate firmware files per part (e.g.,
<keyboard>_left,<keyboard>_right). - Keymap changes typically only require flashing the central; configuration changes may require flashing all parts.
- Some behaviors have locality constraints that affect which side executes them.
Related
- bluetooth
- battery
- troubleshooting/connection-issues
- config/split
ZMK Studio
Enables runtime keymap updates without reflashing firmware. Users can modify keyboard layers while the device is in use via USB or Bluetooth at https://zmk.studio/ or through native desktop apps.
Signature / Usage
# build.yaml (GitHub Actions)
- board: <your_board>
shield: <your_shield>
snippet: studio-rpc-usb-uart
cmake-args: -DCONFIG_ZMK_STUDIO=y# Local build
west build -S studio-rpc-usb-uart -- -DCONFIG_ZMK_STUDIO=y/* keymap — add unlock binding */
&studio_unlock
/* Reserve extra layers for Studio */
/ {
keymap {
compatible = "zmk,keymap";
extra_layer {
status = "reserved";
};
};
};Options / Props
| Symbol | Description |
|---|---|
CONFIG_ZMK_STUDIO | Master enable flag for ZMK Studio support |
studio-rpc-usb-uart | Build snippet enabling the RPC transport over USB UART |
Notes
- Once Studio has written keymap changes to the device, future edits to the
.keymapsource file will not be applied unless a factory reset is performed via the Studio UI. - Keyboards must have physical layouts with a
keysproperty defined. - Boards using
chosen zmk,matrix-transformare not compatible with Studio. - Some MCUs (e.g., STM32F072) require RAM optimization tuning.
- Deep Sleep clears unsaved Studio changes.
- Currently supported: layer renaming, enabling extra layers, key binding assignment, alternative physical layout selection.
- Planned (not yet available): behavior property configuration, combos, conditional layers, host locale selection, keymap import/export.
Related
- low-power-states
- bluetooth
Customizing ZMK
ZMK configuration lives in a personal zmk-config repository, keeping user settings separate from the ZMK firmware source. Changes are committed and pushed to GitHub, where Actions automatically build updated firmware.
Configuration Files
Each keyboard in a zmk-config has two primary files under the config/ subfolder:
| File | Purpose |
|---|---|
<keyboard>.conf | Feature toggles and firmware options (Kconfig format) |
<keyboard>.keymap | Key assignments and layer definitions |
The build.yaml file at the repository root specifies which board/shield combinations to build.
Workflow
1. Edit config/<keyboard>.conf to enable or disable firmware features. 2. Edit config/<keyboard>.keymap to define your key layout and layers. 3. Update build.yaml if adding or removing a keyboard. 4. Commit and push — GitHub Actions builds and publishes firmware artifacts. 5. Download and flash the firmware to your keyboard.
Multi-Keyboard Support
To manage multiple keyboards in one repository, add entries to build.yaml and place each keyboard's .conf and .keymap files in the config/ subfolder.
Notes
- For split keyboards, only the central (left) half needs to be reflashed when updating keymaps.
- Local builds are supported for users who prefer not to rely on GitHub Actions — follow the toolchain setup documentation.
- The
zmk codecommand (ZMK CLI) opens keymap and config files in your preferred editor.
Related
- Installing ZMK
- ZMK CLI
FAQ
Frequently asked questions about ZMK Firmware.
General
Why Zephyr? Zephyr is a best-in-class RTOS offering cross-architecture support, low-power optimization, DeviceTree/Kconfig configuration, a qualified BLE stack, multi-processor support, Apache 2.0 licensing, and LTS security updates.
Why create ZMK? ZMK offers a Zephyr foundation, permissive MIT licensing (enabling integration of non-GPL-compatible libraries), and a wireless-first design prioritizing power efficiency.
What is the license? ZMK uses the MIT license.
What does "ZMK" stand for? "Zephyr Mechanical Keyboard" — inspired by the naming convention of TMK/QMK.
Who created ZMK? Pete Johanson.
Is ZMK related to TMK/QMK? Inspired by them, but it is an entirely separate and unrelated project.
Hardware
What hardware is supported? ZMK can theoretically run on any Zephyr-supported platform; contributors test a limited subset of configurations. See Supported Hardware.
Does ZMK support AVR? No. Zephyr only supports 32-bit and 64-bit platforms.
What are boards vs. shields? Zephyr terminology that enables modularity — different compatible controllers can be combined with different keyboard PCBs (shields) at build time.
Features
Does ZMK support wired split keyboards? Currently, ZMK only supports wireless split keyboards. Experimental wired support exists for advanced users with specific hardware.
What is the input latency? Comparable to other keyboard firmware; varies by scanning method and debounce algorithm.
Is there a 2.4 GHz dongle option? No current plans. Nordic's proprietary 2.4 GHz protocols require restrictive licensing. BLE dongles are available with approximately 3.75 ms theoretical latency.
Is ZMK bootloader-specific? No. ZMK supports multiple flash utilities including OpenOCD and nrfjprog.
Community
How do I contribute? Use the developer documentation to get started.
Where do I discuss ideas or new keyboard support? Join the Discord community.
Is there a Code of Conduct? Yes, available on GitHub.
Notes
- The project team is small; support capacity is limited while development is ongoing.
- Community channels: Discord, Mastodon, Twitter, GitHub.
Related
- Supported Hardware
- Installing ZMK
Supported Hardware
ZMK supports a wide range of microcontroller platforms built on Zephyr RTOS, including Nordic nRF52, Raspberry Pi RP2040/RP2350, ST STM32, and Microchip SAMD21. Hardware is organized into two main categories: onboard controller keyboards and composite keyboards.
Hardware Categories
Onboard Controller Keyboards
Single-PCB keyboards where the controller is integrated directly on the board. Examples include Advantage 360 Pro, Glove80, and Planck Rev6.
Composite Keyboards
Separate controller boards paired with keyboard PCBs (shields), organized by interconnect type:
| Interconnect | Example Controllers | Example Shields |
|---|---|---|
| Pro Micro | nice!nano, nRFMicro, Adafruit KB2040 | Corne, Kyria, Lily58 |
| Seeed XIAO | XIAO nRF52840, XIAO RP2040 | Hummingbird |
| Arduino Uno Rev3 | Nordic dev kits | ZMK Uno |
| BlackPill | PillBug, BlackPill (STM32) | — |
| MakerDiary nRF52840 M.2 | M.2 form factor modules | — |
Notes
- ZMK does not support AVR 8-bit processors (e.g., original Arduino Uno, SparkFun Pro Micro) because Zephyr only supports 32-bit and 64-bit platforms.
- All in-tree boards use a
zmkboard variant for consistency. - Any Zephyr-supported platform can theoretically run ZMK, but only a subset has been tested by contributors.
- New keyboard shield support requires following the contributor documentation and clean room design requirements.
Related
- FAQ
- Installing ZMK
Getting Started
| Name | Description | Path |
|---|---|---|
| Supported Hardware | Microcontroller platforms, boards, and shields supported by ZMK | ./hardware.md |
| FAQ | Frequently asked questions covering licensing, hardware, features, and community | ./faq.md |
| Installing ZMK | Cloud-based firmware build setup using GitHub Actions and ZMK CLI | ./user-setup.md |
| Customizing ZMK | Editing keymap and configuration files in a zmk-config repository | ./customization.md |
| ZMK CLI | Command-line tool for managing ZMK config repos, keyboards, and modules | ./zmk-cli.md |
Installing ZMK
ZMK uses GitHub Actions to build firmware in the cloud, eliminating the need to set up a local toolchain. Users create a personal zmk-config repository that holds their keyboard configuration, then push changes to trigger automated builds.
Prerequisites
| Tool | Purpose |
|---|---|
| Git | Version control for managing the config repository |
| GitHub account | Cloud builds via GitHub Actions and repository hosting |
| uv | Python package manager used to install ZMK CLI |
Setup Steps
1. Install the ZMK CLI via uv (see ZMK CLI). 2. Run zmk init to create and clone a GitHub repository for your configuration. 3. Edit build.yaml to specify your keyboard board and shield. 4. Customize your <keyboard>.keymap and <keyboard>.conf files. 5. Commit and push changes — GitHub Actions will build the firmware automatically. 6. Download the compiled firmware artifact from the GitHub Actions run.
Flashing Firmware
| File type | Method |
|---|---|
.uf2 | Enter bootloader mode, then copy the file to the mounted drive |
.hex | Flash via DFU using QMK Toolbox |
Notes
- When Git prompts for credentials, do not enter your password. Paste your GitHub personal access token (with
workflowscope) in the password field instead. GitHub CLI can be used as an alternative for authentication. - For split keyboards, test the central half (typically the left side) over USB before attempting wireless pairing.
- Only the central half needs to be reflashed when updating keymaps on a split keyboard.
- Local builds are possible by following the toolchain setup documentation.
Related
- ZMK CLI
- Customizing ZMK
- Supported Hardware
ZMK CLI
The ZMK CLI is a command-line utility that streamlines creation and management of ZMK configuration repositories. It automates common tasks such as initializing a repo, adding keyboards, and downloading firmware.
Installation
The ZMK CLI is installed as part of the user setup process using uv. To update to the latest version:
uv tool upgrade zmkCommands
Repository
| Command | Description |
|---|---|
zmk init | Interactively create and clone a GitHub repository for ZMK config |
zmk cd | Navigate to the config repository directory |
Keyboard
| Command | Description |
|---|---|
zmk keyboard add | Add a keyboard to the build system |
zmk keyboard remove | Remove a keyboard from build configuration |
zmk keyboard list | List supported hardware options |
zmk keyboard new | Generate boilerplate for a new custom keyboard |
Module
| Command | Description |
|---|---|
zmk module add | Add a ZMK module to the configuration |
zmk module list | List available modules |
Editing & Download
| Command | Description |
|---|---|
zmk code | Open keymap and config files in the configured text editor |
zmk download / zmk dl | Open the GitHub Actions page to retrieve built firmware |
Configuration Settings
| Setting | Description |
|---|---|
user.home | Path to the ZMK config repository |
core.editor | Preferred text editor |
core.explorer | Preferred file browser |
Notes
zmk inithandles GitHub authentication and repository creation interactively.- Modules extend ZMK with additional features beyond the core firmware.
Related
- Installing ZMK
- Customizing ZMK