Modular RGB effects for ZMK — per-key layer indicators, battery, HID indicators
Find a file
2026-07-01 15:46:36 +08:00
dts/bindings Initial: modular per-key RGB layer effect for ZMK 2026-07-01 08:46:43 +08:00
include/zmk Initial: modular per-key RGB layer effect for ZMK 2026-07-01 08:46:43 +08:00
src feat: implement all 6 fork parity behaviors 2026-07-01 15:17:06 +08:00
zephyr Initial: modular per-key RGB layer effect for ZMK 2026-07-01 08:46:43 +08:00
CMakeLists.txt Initial: modular per-key RGB layer effect for ZMK 2026-07-01 08:46:43 +08:00
Kconfig Initial: modular per-key RGB layer effect for ZMK 2026-07-01 08:46:43 +08:00
README.md Add README with setup, usage, and custom effect guide 2026-07-01 15:46:36 +08:00

zmk-rgb-effects

Modular RGB effects for ZMK keyboards. Provides per-key per-layer RGB indicators and a framework for adding custom effects without forking ZMK.

Requires: ZMK fork branch rgb-modular-v1.1 (modular effect registration API).

Setup

Add to your west.yml:

manifest:
  remotes:
    - name: afiqzudinhadi
      url-base: https://github.com/afiqzudinhadi
  projects:
    - name: zmk
      remote: afiqzudinhadi
      revision: rgb-modular-v1.1
      import: app/west.yml
    - name: zmk-rgb-effects
      remote: afiqzudinhadi
      revision: v1.1.1

Enable in your .conf:

CONFIG_ZMK_RGB_UNDERGLOW=y
CONFIG_ZMK_RGB_EFFECT_LAYER=y

Per-key layer effect

Define per-key colors in your keymap/overlay using devicetree:

#include <dt-bindings/zmk/rgb_colors.h>

/ {
    underglow-layer {
        compatible = "zmk,underglow-layer";
        pixel-lookup = <
            5  4  3  2  1  0
            6  7  8  9  10 11
            17 16 15 14 13 12
        >;

        layer0 {
            layer-id = <0>;
            bindings = <
                &ugc RGB_RED  &ugc RGB_RED  &ugc RGB_OFF ...
            >;
        };

        layer1 {
            layer-id = <1>;
            fade-delay = <15>;
            bindings = <
                &ugc RGB_BLUE &ugc RGB_BLUE &ugc RGB_OFF ...
            >;
        };
    };
};

Properties

  • pixel-lookup — maps LED chain index to key position
  • layer-id — which keymap layer this color map applies to
  • fade-delay — seconds before reverting to animated effect (-1 = never)
  • bindings — color per key using behaviors below

Behaviors

Behavior Description Params
&ugc Static color param1 = RGB hex color
&ubi Battery indicator param1 = low color, param2 = ok color, threshold DT prop
&ugi HID indicator (caps/num/scroll lock) param1 = off color, param2 = on color, indicator DT prop

Adding custom effects

Create a .c file with ZMK_RGB_EFFECT_DEFINE:

#include <zmk/rgb_effect.h>

static void my_render(struct zmk_rgb_effect_ctx *ctx) {
    for (int i = 0; i < ctx->num_pixels; i++) {
        struct zmk_led_hsb hsb = ctx->base_color;
        hsb.h = (*ctx->animation_step + i * 30) % 360;
        ctx->pixels[i] = zmk_rgb_hsb_to_rgb(zmk_rgb_hsb_scale_min_max(hsb));
    }
    *ctx->animation_step += ctx->animation_speed;
}

ZMK_RGB_EFFECT_DEFINE(my_effect, "My Effect",
    my_render, 0, NULL, NULL, NULL, NULL);

Add to CMakeLists.txt:

target_sources(app PRIVATE src/effects/my_effect.c)

The effect auto-registers via linker section. Accessible by cycling with RGB_EFF.

Effect flags

Flag Description
0 Animated — core runs render at 25ms tick
ZMK_RGB_EFFECT_STATIC No periodic tick — render called on demand via zmk_rgb_request_refresh()
ZMK_RGB_EFFECT_PERSISTENT Survives RGB toggle and idle sleep

Effect callbacks

Callback When called
render Each tick (animated) or on refresh (static)
on_select Effect becomes active
on_deselect User cycles to different effect
on_idle(bool awake) Keyboard sleeps/wakes (persistent effects only)
is_active() Queried for ext power gating — return true to keep LEDs powered

Render context

Field Type Description
pixels struct led_rgb * LED buffer to write
num_pixels uint16_t Strip length
base_color struct zmk_led_hsb User-configured HSB color
animation_step uint16_t * Read/write animation counter
animation_speed uint8_t User-configured speed (1-5)

Utility functions

  • zmk_rgb_hsb_to_rgb(hsb) — HSB to RGB conversion
  • zmk_rgb_hsb_scale_min_max(hsb) — scale brightness to configured min/max
  • zmk_rgb_hsb_scale_zero_max(hsb) — scale brightness from 0 to max
  • zmk_rgb_request_refresh() — trigger re-render for static effects
  • zmk_rgb_request_refresh_wakeup(bool) — refresh with conditional wake from sleep
  • zmk_rgb_set_tick_delay(int seconds) — delayed tick restart (for fade effects)
  • zmk_rgb_is_on() — query RGB on/off state

Versioning

vX.Y.Z where:

  • X — major version (breaking API changes)
  • Y — ZMK core dependency version (matches rgb-modular-vX.Y)
  • Z — custom effects (0 = defaults only, 1+ = custom effects included)