diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 07c78080..71ef2b20 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -44,6 +44,7 @@ target_sources(app PRIVATE src/behaviors/behavior_mod_tap.c)
 target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
 target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
 target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c)
+target_sources(app PRIVATE src/behaviors/behavior_rgb_underglow.c)
 target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c)
 target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL app PRIVATE src/split_listener.c)
 target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL app PRIVATE src/split/bluetooth/service.c)
diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi
index 39784799..04e42b63 100644
--- a/app/dts/behaviors.dtsi
+++ b/app/dts/behaviors.dtsi
@@ -3,4 +3,5 @@
 #include <behaviors/mod_tap.dtsi>
 #include <behaviors/momentary_layer.dtsi>
 #include <behaviors/reset.dtsi>
-#include <behaviors/sensor_rotate_key_press.dtsi>
\ No newline at end of file
+#include <behaviors/sensor_rotate_key_press.dtsi>
+#include <behaviors/rgb_underglow.dtsi>
\ No newline at end of file
diff --git a/app/dts/behaviors/rgb_underglow.dtsi b/app/dts/behaviors/rgb_underglow.dtsi
new file mode 100644
index 00000000..9a1ddcf2
--- /dev/null
+++ b/app/dts/behaviors/rgb_underglow.dtsi
@@ -0,0 +1,9 @@
+/ {
+	behaviors {
+		rgb_ug: behavior_rgb_underglow {
+			compatible = "zmk,behavior-rgb-underglow";
+			label = "RGB_UNDERGLOW_ACTION";
+			#binding-cells = <1>;
+		};
+	};
+};
diff --git a/app/dts/bindings/behaviors/zmk,behavior-rgb-underglow.yaml b/app/dts/bindings/behaviors/zmk,behavior-rgb-underglow.yaml
new file mode 100644
index 00000000..6b6d5b00
--- /dev/null
+++ b/app/dts/bindings/behaviors/zmk,behavior-rgb-underglow.yaml
@@ -0,0 +1,8 @@
+# Copyright (c) 2020, Nick Winans
+# SPDX-License-Identifier: MIT
+
+description: RGB Underglow Action
+
+compatible: "zmk,behavior-rgb-underglow"
+
+include: one_param.yaml
\ No newline at end of file
diff --git a/app/include/dt-bindings/zmk/rgb.h b/app/include/dt-bindings/zmk/rgb.h
new file mode 100644
index 00000000..c2efda88
--- /dev/null
+++ b/app/include/dt-bindings/zmk/rgb.h
@@ -0,0 +1,12 @@
+
+#define RGB_TOG 0
+#define RGB_HUI 1
+#define RGB_HUD 2
+#define RGB_SAI 3
+#define RGB_SAD 4
+#define RGB_BRI 5
+#define RGB_BRD 6
+#define RGB_SPI 7
+#define RGB_SPD 8
+#define RGB_EFF 9
+#define RGB_EFR 10
diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h
new file mode 100644
index 00000000..60754d53
--- /dev/null
+++ b/app/include/zmk/rgb_underglow.h
@@ -0,0 +1,8 @@
+#pragma once
+
+void zmk_rgb_underglow_toggle();
+void zmk_rgb_underglow_cycle_effect(int direction);
+void zmk_rgb_underglow_change_hue(int direction);
+void zmk_rgb_underglow_change_sat(int direction);
+void zmk_rgb_underglow_change_brt(int direction);
+void zmk_rgb_underglow_change_spd(int direction);
diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c
index 9239f370..7c839536 100644
--- a/app/src/behaviors/behavior_rgb_underglow.c
+++ b/app/src/behaviors/behavior_rgb_underglow.c
@@ -10,6 +10,9 @@
 #include <drivers/behavior.h>
 #include <logging/log.h>
 
+#include <dt-bindings/zmk/rgb.h>
+#include <zmk/rgb_underglow.h>
+
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
 struct behavior_rgb_underglow_config { };
@@ -19,3 +22,59 @@ static int behavior_rgb_underglow_init(struct device *dev)
 {
   return 0;
 }
+
+static int on_keymap_binding_pressed(struct device *dev, u32_t position, u32_t action, u32_t _)
+{
+  switch (action)
+  {
+    case RGB_TOG:
+      zmk_rgb_underglow_toggle();
+      break;
+    case RGB_HUI:
+      zmk_rgb_underglow_change_hue(1);
+      break;
+    case RGB_HUD:
+      zmk_rgb_underglow_change_hue(-1);
+      break;
+    case RGB_SAI:
+      zmk_rgb_underglow_change_sat(1);
+      break;
+    case RGB_SAD:
+      zmk_rgb_underglow_change_sat(-1);
+      break;
+    case RGB_BRI:
+      zmk_rgb_underglow_change_brt(1);
+      break;
+    case RGB_BRD:
+      zmk_rgb_underglow_change_brt(-1);
+      break;
+    case RGB_SPI:
+      zmk_rgb_underglow_change_spd(1);
+      break;
+    case RGB_SPD:
+      zmk_rgb_underglow_change_spd(-1);
+      break;
+    case RGB_EFF:
+      zmk_rgb_underglow_cycle_effect(1);
+      break;
+    case RGB_EFR:
+      zmk_rgb_underglow_cycle_effect(-1);
+      break;
+  }
+
+  return 0;
+}
+
+static const struct behavior_driver_api behavior_rgb_underglow_driver_api = {
+  .binding_pressed = on_keymap_binding_pressed,
+};
+
+static const struct behavior_rgb_underglow_config behavior_rgb_underglow_config = {};
+
+static struct behavior_rgb_underglow_data behavior_rgb_underglow_data;
+
+DEVICE_AND_API_INIT(behavior_rgb_underglow, DT_INST_LABEL(0), behavior_rgb_underglow_init,
+                    &behavior_rgb_underglow_data,
+                    &behavior_rgb_underglow_config,
+                    APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
+                    &behavior_rgb_underglow_driver_api);
\ No newline at end of file
diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c
index 5b71aae5..e4287c01 100644
--- a/app/src/rgb_underglow.c
+++ b/app/src/rgb_underglow.c
@@ -6,6 +6,10 @@
 
 #include <device.h>
 #include <init.h>
+#include <kernel.h>
+
+#include <math.h>
+#include <stdlib.h>
 
 #include <logging/log.h>
 
@@ -15,65 +19,277 @@
 
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
-#define STRIP_LABEL		DT_LABEL(DT_ALIAS(led_strip))
+#define STRIP_LABEL		    DT_LABEL(DT_ALIAS(led_strip))
 #define STRIP_NUM_PIXELS	DT_PROP(DT_ALIAS(led_strip), chain_length)
 
-#define DELAY_TIME K_MSEC(50)
-
-#define RGB(_r, _g, _b) { .r = (_r), .g = (_g), .b = (_b) }
-
-static const struct led_rgb colors[] = {
-	RGB(0x0f, 0x00, 0x00), /* red */
-	RGB(0x00, 0x0f, 0x00), /* green */
-	RGB(0x00, 0x00, 0x0f), /* blue */
+enum rgb_underglow_effect {
+    UNDERGLOW_EFFECT_SOLID,
+    UNDERGLOW_EFFECT_BREATHE,
+    UNDERGLOW_EFFECT_SPECTRUM,
+    UNDERGLOW_EFFECT_SWIRL,
+    UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects
 };
 
+struct led_hsb {
+	u16_t h;
+	u8_t  s;
+	u8_t  b;
+};
+
+struct rgb_underglow_state {
+    u16_t hue;
+    u8_t  saturation;
+    u8_t  brightness;
+    u8_t  animation_speed;
+    u8_t  current_effect;
+    u16_t animation_step;
+    bool  on;
+};
+
+struct rgb_underglow_state state;
+
+struct device *led_strip;
+
 struct led_rgb pixels[STRIP_NUM_PIXELS];
 
-static void zmk_rgb_underglow_start()
+static struct led_rgb hsb_to_rgb(struct led_hsb hsb)
 {
-    struct device *strip;
-	size_t cursor = 0, color = 0;
-	int rc;
+    double r, g, b;
 
-	strip = device_get_binding(STRIP_LABEL);
-	if (strip) {
-		LOG_INF("Found LED strip device %s", STRIP_LABEL);
-	} else {
-		LOG_ERR("LED strip device %s not found", STRIP_LABEL);
-		return;
-	}
+    u8_t i = hsb.h / 60;
+    double v = hsb.b / 100.0;
+    double s = hsb.s / 100.0;
+    double f = hsb.h / 360.0 * 6 - i;
+    double p = v * (1 - s);
+    double q = v * (1 - f * s);
+    double t = v * (1 - (1 - f) * s);
 
-	LOG_INF("Displaying pattern on strip");
-	while (1) {
-		memset(&pixels, 0x00, sizeof(pixels));
-		memcpy(&pixels[cursor], &colors[color], sizeof(struct led_rgb));
-		rc = led_strip_update_rgb(strip, pixels, STRIP_NUM_PIXELS);
+    switch (i % 6)
+    {
+        case 0: r = v; g = t; b = p; break;
+        case 1: r = q; g = v; b = p; break;
+        case 2: r = p; g = v; b = t; break;
+        case 3: r = p; g = q; b = v; break;
+        case 4: r = t; g = p; b = v; break;
+        case 5: r = v; g = p; b = q; break;
+    }
 
-		if (rc) {
-			LOG_ERR("couldn't update strip: %d", rc);
-		}
+    struct led_rgb rgb = { r: r*255, g: g*255, b: b*255 };
 
-		cursor++;
-		if (cursor >= STRIP_NUM_PIXELS) {
-			cursor = 0;
-			color++;
-			if (color == ARRAY_SIZE(colors)) {
-				color = 0;
-			}
-		}
-
-		k_sleep(DELAY_TIME);
-	}
+    return rgb;
 }
 
+static void zmk_rgb_underglow_effect_solid()
+{
+    for (int i=0; i<STRIP_NUM_PIXELS; i++)
+    {
+        int hue = state.hue;
+        int sat = state.saturation;
+        int brt = state.brightness;
+
+        struct led_hsb hsb = { hue, sat, brt };
+
+        pixels[i] = hsb_to_rgb(hsb);
+    }
+}
+
+static void zmk_rgb_underglow_effect_breathe()
+{
+    for (int i=0; i<STRIP_NUM_PIXELS; i++)
+    {
+        int hue = state.hue;
+        int sat = state.saturation;
+        int brt = abs(state.animation_step - 1200) / 12;
+
+        struct led_hsb hsb = { hue, sat, brt };
+
+        pixels[i] = hsb_to_rgb(hsb);
+    }
+
+    state.animation_step += state.animation_speed * 10;
+    
+    if (state.animation_step > 2400) {
+        state.animation_step = 0;
+    }
+}
+
+static void zmk_rgb_underglow_effect_spectrum()
+{
+    for (int i=0; i<STRIP_NUM_PIXELS; i++)
+    {
+        int hue = state.animation_step;
+        int sat = state.saturation;
+        int brt = state.brightness;
+
+        struct led_hsb hsb = { hue, sat, brt };
+
+        pixels[i] = hsb_to_rgb(hsb);
+    }
+
+    state.animation_step += state.animation_speed;
+    state.animation_step = state.animation_step % 360;
+}
+
+static void zmk_rgb_underglow_effect_swirl()
+{
+    for (int i=0; i<STRIP_NUM_PIXELS; i++)
+    {
+        int hue = (360 / STRIP_NUM_PIXELS * i + state.animation_step) % 360;
+        int sat = state.saturation;
+        int brt = state.brightness;
+
+        struct led_hsb hsb = { hue, sat, brt };
+
+        pixels[i] = hsb_to_rgb(hsb);
+    }
+
+    state.animation_step += state.animation_speed * 2;
+    state.animation_step = state.animation_step % 360;
+}
+
+static void zmk_rgb_underglow_tick(struct k_work *work)
+{
+    switch (state.current_effect)
+    {
+        case UNDERGLOW_EFFECT_SOLID:
+            zmk_rgb_underglow_effect_solid();
+            break;
+        case UNDERGLOW_EFFECT_BREATHE:
+            zmk_rgb_underglow_effect_breathe();
+            break;
+        case UNDERGLOW_EFFECT_SPECTRUM:
+            zmk_rgb_underglow_effect_spectrum();
+            break;
+        case UNDERGLOW_EFFECT_SWIRL:
+            zmk_rgb_underglow_effect_swirl();
+            break;
+    }
+
+    led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS);
+}
+
+K_WORK_DEFINE(underglow_work, zmk_rgb_underglow_tick);
+
+static void zmk_rgb_underglow_tick_handler(struct k_timer *timer)
+{
+    k_work_submit(&underglow_work);
+}
+
+K_TIMER_DEFINE(underglow_tick, zmk_rgb_underglow_tick_handler, NULL);
+
 static int zmk_rgb_underglow_init(struct device *_arg)
 {
-    zmk_rgb_underglow_start();
+	led_strip = device_get_binding(STRIP_LABEL);
+	if (led_strip) {
+		LOG_INF("Found LED strip device %s", STRIP_LABEL);
+	} else {
+		LOG_ERR("LED strip device %s not found", STRIP_LABEL);
+		return 1;
+	}
+
+    state = (struct rgb_underglow_state){
+        hue: 0,
+        saturation: 100,
+        brightness: 100,
+        animation_speed: 3,
+        current_effect: 0,
+        animation_step: 0,
+        on: true
+    };
+
+    k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50));
 
     return 0;
 }
 
+void zmk_rgb_underglow_cycle_effect(int direction)
+{
+    if (state.current_effect == 0 && direction < 0) {
+        state.current_effect = UNDERGLOW_EFFECT_NUMBER - 1;
+    } else {
+        state.current_effect += direction;
+
+        if (state.current_effect >= UNDERGLOW_EFFECT_NUMBER) {
+            state.current_effect = 0;
+        }
+    }
+    
+    state.animation_step = 0;
+}
+
+void zmk_rgb_underglow_toggle()
+{
+    state.on = !state.on;
+
+    if (state.on) {
+        state.animation_step = 0;
+        k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50));
+    } else {
+
+        for (int i=0; i<STRIP_NUM_PIXELS; i++)
+        {
+            pixels[i] = (struct led_rgb){ r: 0, g: 0, b: 0};
+        }
+
+        led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS);
+
+        k_timer_stop(&underglow_tick);
+    }
+}
+
+void zmk_rgb_underglow_change_hue(int direction)
+{
+    if (state.hue == 0 && direction < 0) {
+        state.hue = 350;
+        return;
+    }
+    
+    state.hue += direction * CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP;
+
+    if (state.hue > 350) {
+        state.hue = 0;
+    }
+}
+
+void zmk_rgb_underglow_change_sat(int direction)
+{
+    if (state.saturation == 0 && direction < 0) {
+        return;
+    }
+
+    state.saturation += direction * CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP;
+
+    if (state.saturation > 100) {
+        state.saturation = 100;
+    }
+}
+
+void zmk_rgb_underglow_change_brt(int direction)
+{
+    if (state.brightness == 0 && direction < 0) {
+        return;
+    }
+
+    state.brightness += direction * CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP;
+
+    if (state.brightness > 100) {
+        state.brightness = 100;
+    }
+}
+
+void zmk_rgb_underglow_change_spd(int direction)
+{
+    if (state.animation_speed == 1 && direction < 0) {
+        return;
+    }
+
+    state.animation_speed += direction;
+
+    if (state.animation_speed > 5) {
+        state.animation_speed = 5;
+    }
+}
+
 SYS_INIT(zmk_rgb_underglow_init,
         APPLICATION,
         CONFIG_APPLICATION_INIT_PRIORITY);