LED indicators using an RGB LED

February 18, 2026 ยท View on GitHub

Important

This module uses a versioning scheme that is compatible with ZMK versions. As a general rule, the main branch is targeting compatibility with ZMK's main.

If you have build failures with ZMK's latest release (like v0.3) make sure to use the corresponding revision for zmk-rgbled-widget in your west.yml.

This is a ZMK module containing a simple widget that utilizes a (typically built-in) RGB LED controlled by three separate GPIOs. It is used to indicate battery level and BLE connection status in a minimalist way.

Features

Short video demo See below video for a short demo, running through power on, profile switching and power offs.

https://github.com/caksoylar/zmk-rgbled-widget/assets/7876996/cfd89dd1-ff24-4a33-8563-2fdad2a828d4

Battery status

  • Blink ๐ŸŸข/๐ŸŸก/๐Ÿ”ด on boot depending on battery level, with thresholds set by CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_HIGH and CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_LOW
    • See options for showing battery levels for splits
  • Blink ๐Ÿ”ด on every battery level change if below critical battery level (CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_CRITICAL)

Connection status

  • Blink ๐Ÿ”ต for connected, ๐ŸŸก for open (advertising), ๐Ÿ”ด for disconnected profiles on boot after the battery blink, and following every BT profile switch (only on central side for splits)
    • Enable CONFIG_RGBLED_WIDGET_CONN_SHOW_USB to blink cyan if USB currently has priority over BLE, instead of above
  • Blink ๐Ÿ”ต for connected, ๐Ÿ”ด for disconnected on peripheral side of splits

Layer state

You can pick one of the following methods (off by default) to indicate the highest active layer:

  • Enable CONFIG_RGBLED_WIDGET_SHOW_LAYER_CHANGE to show the highest active layer on every layer activation using a sequence of N cyan color blinks, where N is the zero-based index of the layer, or
  • Enable CONFIG_RGBLED_WIDGET_SHOW_LAYER_COLORS to assign each layer its own color, which will remain on while that layer is the highest active layer

These layer indicators will only be active on the central part of a split keyboard, since peripheral parts aren't aware of the layer information.

Tip

Also see below for keymap behaviors you can use to show the battery and connection status on demand.

Installation

To use, first add this module to your config/west.yml by adding a new entry to projects:

manifest:
  remotes:
    - name: zmkfirmware
      url-base: https://github.com/zmkfirmware
  projects:
    - name: zmk
      remote: zmkfirmware
      revision: v0.3           # Your ZMK version
      import: app/west.yml
    - name: zmk-rgbled-widget  # <-- new entry
      url: https://github.com/caksoylar/zmk-rgbled-widget
      revision: v0.3           # MUST match your ZMK version!
  self:
    path: config

For more information, including instructions for building locally, check out the ZMK docs on building with modules.

Then, if you are using one of the boards supported by the rgbled_adapter shield such as Xiao BLE, just add the rgbled_adapter as an additional shield to your build, e.g. in build.yaml:

---
include:
  - board: xiao_ble//zmk
    shield: hummingbird rgbled_adapter

For other keyboards, see the "Adding support" section below.

Showing status on demand

This module also defines keymap behaviors to let you show battery or connection status on demand:

#include <behaviors/rgbled_widget.dtsi>  // needed to use the behaviors

/ {
    keymap {
        ...
        some_layer {
            bindings = <
                ...
                &ind_bat  // indicate battery level
                &ind_con  // indicate connectivity status
                ...
            >;
        };
    };
};

When you invoke the behavior by pressing the corresponding key (or combo), it will trigger the LED color display. This will happen on all keyboard parts for split keyboards, so make sure to flash firmware to all parts after enabling.

Note

The behaviors can be used even when you use split keyboards with different controllers that don't all support the widget. Make sure that you use the rgbled_adapter shield (or enable CONFIG_RGBLED_WIDGET if not using the adapter) only for the keyboard parts that support it.

Battery levels for splits

For split keyboards, each part will indicate its own battery level with a single battery blink, by default. However, for some scenarios like keyboards with dongles and no RGBLED widget on the peripherals, you might want the central part to show the battery levels of peripherals too. This can be done by enabling one of the below settings:

  • CONFIG_RGBLED_WIDGET_BATTERY_SHOW_PERIPHERALS: Blink for battery level of self and then the peripherals, in order
  • CONFIG_RGBLED_WIDGET_BATTERY_SHOW_ONLY_PERIPHERALS: Blink for battery level of only the peripherals, in order

These two settings only apply to split central parts. The order of blinks for peripherals is determined by the initial pairing order for the split parts. If a part is currently disconnected, a magenta/purple (configurable) blink will be displayed.

Configuration details

General
NameDescriptionDefault
CONFIG_RGBLED_WIDGET_INTERVAL_MSMinimum wait duration between two blinks in ms500
Battery-related
NameDescriptionDefault
CONFIG_RGBLED_WIDGET_BATTERY_BLINK_MSDuration of battery level blink in ms2000
CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_HIGHHigh battery level percentage80
CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_LOWLow battery level percentage20
CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_CRITICALCritical battery level percentage, blink periodically if under5
CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_CRITICALCritical battery level percentage, blink periodically if under5
CONFIG_RGBLED_WIDGET_BATTERY_COLOR_HIGHColor for high battery level (above LEVEL_HIGH)Green (2)
CONFIG_RGBLED_WIDGET_BATTERY_COLOR_MEDIUMColor for medium battery level (between LEVEL_LOW and LEVEL_HIGH)Yellow (3)
CONFIG_RGBLED_WIDGET_BATTERY_COLOR_LOWColor for low battery level (below LEVEL_LOW)Red (1)
CONFIG_RGBLED_WIDGET_BATTERY_COLOR_CRITICALColor for critical battery level (below LEVEL_CRITICAL)Red (1)
CONFIG_RGBLED_WIDGET_BATTERY_COLOR_MISSINGColor for battery not detected, or peripheral disconnectedMagenta (5)

Only one of the options below can be enabled. The non-default ones (second and third below) only work on central parts of splits.

NameDescriptionDefault
CONFIG_RGBLED_WIDGET_BATTERY_SHOW_SELFIndicate battery level from self onlyn
CONFIG_RGBLED_WIDGET_BATTERY_SHOW_PERIPHERALSOn a split central, also show peripheral battery levelsn
CONFIG_RGBLED_WIDGET_BATTERY_SHOW_ONLY_PERIPHERALSOn a split central, show only peripheral battery levelsn
Connectivity-related
NameDescriptionDefault
CONFIG_RGBLED_WIDGET_CONN_BLINK_MSDuration of BLE connection status blink in ms1000
CONFIG_RGBLED_WIDGET_CONN_SHOW_USBShow USB indicator instead of BLE status if it has priorityn
CONFIG_RGBLED_WIDGET_CONN_COLOR_CONNECTEDColor for connected BLE connection statusBlue (4)
CONFIG_RGBLED_WIDGET_CONN_COLOR_ADVERTISINGColor for advertising BLE connection statusYellow (3)
CONFIG_RGBLED_WIDGET_CONN_COLOR_DISCONNECTEDColor for disconnected BLE connection statusRed (1)
CONFIG_RGBLED_WIDGET_CONN_COLOR_USBColor for USB endpoint activeCyan (6)
Layers-related

Layer indicator only works on non-splits and central parts of splits.

Below enable and configure the sequence-based layer indicator.

NameDescriptionDefault
CONFIG_RGBLED_WIDGET_SHOW_LAYER_CHANGEIndicate highest active layer on each layer change with a sequence of blinksn
CONFIG_RGBLED_WIDGET_LAYER_BLINK_MSBlink and wait duration for layer indicator100
CONFIG_RGBLED_WIDGET_LAYER_COLORColor to use for layer indicatorCyan (6)
CONFIG_RGBLED_WIDGET_LAYER_DEBOUNCE_MSWait duration after a layer change before showing the highest active layer100

Below enable and configure the color-based layer indicator.

NameDescriptionDefault
CONFIG_RGBLED_WIDGET_SHOW_LAYER_COLORSIndicate highest active layer with a constant configurable color per layern
CONFIG_RGBLED_WIDGET_LAYER_0_COLORColor to use for the base layerBlack (0)
CONFIG_RGBLED_WIDGET_LAYER_1_COLORColor to use for layer 1Red (1)
CONFIG_RGBLED_WIDGET_LAYER_2_COLORColor to use for layer 2Green (2)
CONFIG_RGBLED_WIDGET_LAYER_3_COLORColor to use for layer 3Yellow (3)
CONFIG_RGBLED_WIDGET_LAYER_4_COLORColor to use for layer 4Blue (4)
CONFIG_RGBLED_WIDGET_LAYER_5_COLORColor to use for layer 5Magenta (5)
CONFIG_RGBLED_WIDGET_LAYER_6_COLORColor to use for layer 6Cyan (6)
CONFIG_RGBLED_WIDGET_LAYER_7_COLORColor to use for layer 7White (7)
CONFIG_RGBLED_WIDGET_LAYER_xx_COLORColor to use for layer xx (change xx to the layer number to change)Black (0)
Mapping for color values Color settings use the following integer values:
ColorValue
Black (none)0
Red1
Green2
Yellow3
Blue4
Magenta5
Cyan6
White7

You can add these settings to your keyboard conf file to modify the config values, e.g. in config/hummingbird.conf:

CONFIG_RGBLED_WIDGET_INTERVAL_MS=250
CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_HIGH=50
CONFIG_RGBLED_WIDGET_BATTERY_LEVEL_CRITICAL=10

Adding support in custom boards/shields

To be able to use this widget, you need three LEDs controlled by GPIOs (not smart LEDs), ideally red, green and blue colors. Once you have these LED definitions in your board/shield, simply set the appropriate aliases to the RGB LED node labels.

As an example, here is a definition for three LEDs connected to VCC and separate GPIOs for a nRF52840 controller:

/ {
    aliases {
        led-red = &led0;
        led-green = &led1;
        led-blue = &led2;
    };

    leds {
        compatible = "gpio-leds";
        status = "okay";
        led0: led_0 {
            gpios = <&gpio0 26 GPIO_ACTIVE_LOW>;  // red LED, connected to P0.26
        };
        led1: led_1 {
            gpios = <&gpio0 30 GPIO_ACTIVE_LOW>;  // green LED, connected to P0.30
        };
        led2: led_2 {
            gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;  // blue LED, connected to P0.06
        };
    };
};

(If the LEDs are wired between GPIO and GND instead, use GPIO_ACTIVE_HIGH flag.)

Finally, turn on the widget in the configuration:

CONFIG_RGBLED_WIDGET=y