diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 8a3971e8..f76effc0 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -37,11 +37,13 @@ target_sources(app PRIVATE src/events/keycode_state_changed.c)
 target_sources(app PRIVATE src/events/modifiers_state_changed.c)
 target_sources(app PRIVATE src/events/sensor_event.c)
 target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c)
+target_sources_ifdef(CONFIG_USB app PRIVATE src/events/usb_conn_state_changed.c)
 if (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL)
   target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
   target_sources(app PRIVATE src/behaviors/behavior_reset.c)
   target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
   target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
+  target_sources(app PRIVATE src/behaviors/behavior_outputs.c)
   target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c)
   target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
   target_sources(app PRIVATE src/behaviors/behavior_none.c)
diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi
index 36c918cf..a120b84b 100644
--- a/app/dts/behaviors.dtsi
+++ b/app/dts/behaviors.dtsi
@@ -10,3 +10,4 @@
 #include <behaviors/rgb_underglow.dtsi>
 #include <behaviors/bluetooth.dtsi>
 #include <behaviors/ext_power.dtsi>
+#include <behaviors/outputs.dtsi>
diff --git a/app/dts/behaviors/outputs.dtsi b/app/dts/behaviors/outputs.dtsi
new file mode 100644
index 00000000..a534cbf9
--- /dev/null
+++ b/app/dts/behaviors/outputs.dtsi
@@ -0,0 +1,9 @@
+/ {
+    behaviors {
+        out: behavior_outputs {
+            compatible = "zmk,behavior-outputs";
+            label = "OUTPUTS";
+            #binding-cells = <1>;
+        };
+    };
+};
diff --git a/app/dts/bindings/behaviors/zmk,behavior-outputs.yaml b/app/dts/bindings/behaviors/zmk,behavior-outputs.yaml
new file mode 100644
index 00000000..8bcefd94
--- /dev/null
+++ b/app/dts/bindings/behaviors/zmk,behavior-outputs.yaml
@@ -0,0 +1,10 @@
+#
+# Copyright (c) 2020, The ZMK Contributors
+# SPDX-License-Identifier: MIT
+#
+
+description: Output Selection Behavior
+
+compatible: "zmk,behavior-outputs"
+
+include: one_param.yaml
diff --git a/app/include/dt-bindings/zmk/outputs.h b/app/include/dt-bindings/zmk/outputs.h
new file mode 100644
index 00000000..f24380f7
--- /dev/null
+++ b/app/include/dt-bindings/zmk/outputs.h
@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define OUT_TOG 0
+#define OUT_USB 1
+#define OUT_BLE 2
\ No newline at end of file
diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h
index 1cf71a77..56980c69 100644
--- a/app/include/zmk/ble.h
+++ b/app/include/zmk/ble.h
@@ -15,6 +15,7 @@ int zmk_ble_prof_prev();
 int zmk_ble_prof_select(u8_t index);
 
 bt_addr_le_t *zmk_ble_active_profile_addr();
+bool zmk_ble_active_profile_is_connected();
 char *zmk_ble_active_profile_name();
 
 int zmk_ble_unpair_all();
diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h
index aad6265b..aad688e7 100644
--- a/app/include/zmk/endpoints.h
+++ b/app/include/zmk/endpoints.h
@@ -9,4 +9,12 @@
 #include <zmk/keys.h>
 #include <zmk/hid.h>
 
+enum zmk_endpoint {
+    ZMK_ENDPOINT_USB,
+    ZMK_ENDPOINT_BLE,
+};
+
+int zmk_endpoints_select(enum zmk_endpoint endpoint);
+int zmk_endpoints_toggle();
+
 int zmk_endpoints_send_report(u8_t usage_report);
diff --git a/app/include/zmk/events/usb-conn-state-changed.h b/app/include/zmk/events/usb-conn-state-changed.h
new file mode 100644
index 00000000..d6cc6985
--- /dev/null
+++ b/app/include/zmk/events/usb-conn-state-changed.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <zephyr.h>
+#include <usb/usb_device.h>
+
+#include <zmk/event-manager.h>
+#include <zmk/usb.h>
+
+struct usb_conn_state_changed {
+    struct zmk_event_header header;
+    enum zmk_usb_conn_state conn_state;
+};
+
+ZMK_EVENT_DECLARE(usb_conn_state_changed);
\ No newline at end of file
diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h
index 1ce5cc9b..fd09a6f4 100644
--- a/app/include/zmk/hid.h
+++ b/app/include/zmk/hid.h
@@ -168,9 +168,11 @@ int zmk_hid_register_mods(zmk_mod_flags modifiers);
 int zmk_hid_unregister_mods(zmk_mod_flags modifiers);
 int zmk_hid_keypad_press(zmk_key key);
 int zmk_hid_keypad_release(zmk_key key);
+void zmk_hid_keypad_clear();
 
 int zmk_hid_consumer_press(zmk_key key);
 int zmk_hid_consumer_release(zmk_key key);
+void zmk_hid_consumer_clear();
 
 struct zmk_hid_keypad_report *zmk_hid_get_keypad_report();
 struct zmk_hid_consumer_report *zmk_hid_get_consumer_report();
diff --git a/app/include/zmk/usb.h b/app/include/zmk/usb.h
index 452fd54d..30461de2 100644
--- a/app/include/zmk/usb.h
+++ b/app/include/zmk/usb.h
@@ -12,8 +12,18 @@
 #include <zmk/keys.h>
 #include <zmk/hid.h>
 
+enum zmk_usb_conn_state {
+    ZMK_USB_CONN_NONE,
+    ZMK_USB_CONN_POWERED,
+    ZMK_USB_CONN_HID,
+};
+
 enum usb_dc_status_code zmk_usb_get_status();
+enum zmk_usb_conn_state zmk_usb_get_conn_state();
+
+static inline bool zmk_usb_is_powered() { return zmk_usb_get_conn_state() != ZMK_USB_CONN_NONE; }
+static inline bool zmk_usb_is_hid_ready() { return zmk_usb_get_conn_state() == ZMK_USB_CONN_HID; }
 
 #ifdef CONFIG_ZMK_USB
-int zmk_usb_hid_send_report(u8_t *report, size_t len);
+int zmk_usb_hid_send_report(const u8_t *report, size_t len);
 #endif /* CONFIG_ZMK_USB */
\ No newline at end of file
diff --git a/app/src/behaviors/behavior_outputs.c b/app/src/behaviors/behavior_outputs.c
new file mode 100644
index 00000000..e5182bd4
--- /dev/null
+++ b/app/src/behaviors/behavior_outputs.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_behavior_outputs
+
+#include <device.h>
+#include <devicetree.h>
+#include <drivers/behavior.h>
+
+#include <dt-bindings/zmk/outputs.h>
+
+#include <zmk/behavior.h>
+#include <zmk/endpoints.h>
+
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
+                                     struct zmk_behavior_binding_event event) {
+    switch (binding->param1) {
+    case OUT_TOG:
+        return zmk_endpoints_toggle();
+    case OUT_USB:
+        return zmk_endpoints_select(ZMK_ENDPOINT_USB);
+    case OUT_BLE:
+        return zmk_endpoints_select(ZMK_ENDPOINT_BLE);
+    default:
+        LOG_ERR("Unknown output command: %d", binding->param1);
+    }
+
+    return -ENOTSUP;
+}
+
+static int behavior_out_init(struct device *dev) { return 0; }
+
+static const struct behavior_driver_api behavior_outputs_driver_api = {
+    .binding_pressed = on_keymap_binding_pressed,
+};
+
+DEVICE_AND_API_INIT(behavior_out, DT_INST_LABEL(0), behavior_out_init, NULL, NULL, APPLICATION,
+                    CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_outputs_driver_api);
diff --git a/app/src/ble.c b/app/src/ble.c
index 9090582c..f3962ae0 100644
--- a/app/src/ble.c
+++ b/app/src/ble.c
@@ -94,6 +94,12 @@ static void raise_profile_changed_event() {
     ZMK_EVENT_RAISE(ev);
 }
 
+static void raise_profile_changed_event_callback(struct k_work *work) {
+    raise_profile_changed_event();
+}
+
+K_WORK_DEFINE(raise_profile_changed_event_work, raise_profile_changed_event_callback);
+
 static bool active_profile_is_open() {
     return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY);
 }
@@ -111,7 +117,7 @@ void set_profile_address(u8_t index, const bt_addr_le_t *addr) {
     raise_profile_changed_event();
 }
 
-bool active_profile_is_connected() {
+bool zmk_ble_active_profile_is_connected() {
     struct bt_conn *conn;
     bt_addr_le_t *addr = zmk_ble_active_profile_addr();
     if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) {
@@ -163,9 +169,9 @@ int update_advertising() {
     struct bt_conn *conn;
     enum advertising_type desired_adv = ZMK_ADV_NONE;
 
-    if (active_profile_is_open() || !active_profile_is_connected()) {
+    if (active_profile_is_open()) {
         desired_adv = ZMK_ADV_CONN;
-    } else if (!active_profile_is_connected()) {
+    } else if (!zmk_ble_active_profile_is_connected()) {
         desired_adv = ZMK_ADV_CONN;
         // Need to fix directed advertising for privacy centrals. See
         // https://github.com/zephyrproject-rtos/zephyr/pull/14984 char
@@ -327,6 +333,10 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c
 struct settings_handler profiles_handler = {.name = "ble", .h_set = ble_profiles_handle_set};
 #endif /* IS_ENABLED(CONFIG_SETTINGS) */
 
+static bool is_conn_active_profile(const struct bt_conn *conn) {
+    return bt_addr_le_cmp(bt_conn_get_dst(conn), &profiles[active_profile].peer) == 0;
+}
+
 static void connected(struct bt_conn *conn, u8_t err) {
     char addr[BT_ADDR_LE_STR_LEN];
     bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
@@ -352,6 +362,11 @@ static void connected(struct bt_conn *conn, u8_t err) {
     }
 
     update_advertising();
+
+    if (is_conn_active_profile(conn)) {
+        LOG_DBG("Active profile connected");
+        raise_profile_changed_event();
+    }
 }
 
 static void disconnected(struct bt_conn *conn, u8_t reason) {
@@ -364,6 +379,11 @@ static void disconnected(struct bt_conn *conn, u8_t reason) {
     // We need to do this in a work callback, otherwise the advertising update will still see the
     // connection for a profile as active, and not start advertising yet.
     k_work_submit(&update_advertising_work);
+
+    if (is_conn_active_profile(conn)) {
+        LOG_DBG("Active profile disconnected");
+        k_work_submit(&raise_profile_changed_event_work);
+    }
 }
 
 static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) {
diff --git a/app/src/endpoints.c b/app/src/endpoints.c
index 79d294ef..0c795890 100644
--- a/app/src/endpoints.c
+++ b/app/src/endpoints.c
@@ -4,58 +4,238 @@
  * SPDX-License-Identifier: MIT
  */
 
+#include <init.h>
+#include <settings/settings.h>
+
+#include <zmk/ble.h>
 #include <zmk/endpoints.h>
 #include <zmk/hid.h>
 #include <zmk/usb.h>
 #include <zmk/hog.h>
+#include <zmk/event-manager.h>
+#include <zmk/events/ble-active-profile-changed.h>
+#include <zmk/events/usb-conn-state-changed.h>
 
 #include <logging/log.h>
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
+#define DEFAULT_ENDPOINT                                                                           \
+    COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BLE), (ZMK_ENDPOINT_BLE), (ZMK_ENDPOINT_USB))
+
+static enum zmk_endpoint current_endpoint = DEFAULT_ENDPOINT;
+static enum zmk_endpoint preferred_endpoint =
+    ZMK_ENDPOINT_USB; /* Used if multiple endpoints are ready */
+
+static void update_current_endpoint();
+
+int zmk_endpoints_select(enum zmk_endpoint endpoint) {
+    LOG_DBG("Selected endpoint %d", endpoint);
+
+    if (preferred_endpoint == endpoint) {
+        return 0;
+    }
+
+    preferred_endpoint = endpoint;
+
+#if IS_ENABLED(CONFIG_SETTINGS)
+    settings_save_one("endpoints/preferred", &preferred_endpoint, sizeof(preferred_endpoint));
+#endif
+
+    update_current_endpoint();
+
+    return 0;
+}
+
+int zmk_endpoints_toggle() {
+    enum zmk_endpoint new_endpoint =
+        (preferred_endpoint == ZMK_ENDPOINT_USB) ? ZMK_ENDPOINT_BLE : ZMK_ENDPOINT_USB;
+    return zmk_endpoints_select(new_endpoint);
+}
+
+static int send_keypad_report() {
+    struct zmk_hid_keypad_report *keypad_report = zmk_hid_get_keypad_report();
+
+    switch (current_endpoint) {
+#if IS_ENABLED(CONFIG_ZMK_USB)
+    case ZMK_ENDPOINT_USB: {
+        int err = zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(*keypad_report));
+        if (err) {
+            LOG_ERR("FAILED TO SEND OVER USB: %d", err);
+        }
+        return err;
+    }
+#endif /* IS_ENABLED(CONFIG_ZMK_USB) */
+
+#if IS_ENABLED(CONFIG_ZMK_BLE)
+    case ZMK_ENDPOINT_BLE: {
+        int err = zmk_hog_send_keypad_report(&keypad_report->body);
+        if (err) {
+            LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
+        }
+        return err;
+    }
+#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */
+
+    default:
+        LOG_ERR("Unsupported endpoint %d", current_endpoint);
+        return -ENOTSUP;
+    }
+}
+
+static int send_consumer_report() {
+    struct zmk_hid_consumer_report *consumer_report = zmk_hid_get_consumer_report();
+
+    switch (current_endpoint) {
+#if IS_ENABLED(CONFIG_ZMK_USB)
+    case ZMK_ENDPOINT_USB: {
+        int err = zmk_usb_hid_send_report((u8_t *)consumer_report, sizeof(*consumer_report));
+        if (err) {
+            LOG_ERR("FAILED TO SEND OVER USB: %d", err);
+        }
+        return err;
+    }
+#endif /* IS_ENABLED(CONFIG_ZMK_USB) */
+
+#if IS_ENABLED(CONFIG_ZMK_BLE)
+    case ZMK_ENDPOINT_BLE: {
+        int err = zmk_hog_send_consumer_report(&consumer_report->body);
+        if (err) {
+            LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
+        }
+        return err;
+    }
+#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */
+
+    default:
+        LOG_ERR("Unsupported endpoint %d", current_endpoint);
+        return -ENOTSUP;
+    }
+}
+
 int zmk_endpoints_send_report(u8_t usage_page) {
-    int err;
-    struct zmk_hid_keypad_report *keypad_report;
-    struct zmk_hid_consumer_report *consumer_report;
+
     LOG_DBG("usage page 0x%02X", usage_page);
     switch (usage_page) {
     case USAGE_KEYPAD:
-        keypad_report = zmk_hid_get_keypad_report();
-#ifdef CONFIG_ZMK_USB
-        if (zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(struct zmk_hid_keypad_report)) !=
-            0) {
-            LOG_DBG("USB Send Failed");
-        }
-#endif /* CONFIG_ZMK_USB */
-
-#ifdef CONFIG_ZMK_BLE
-        err = zmk_hog_send_keypad_report(&keypad_report->body);
-        if (err) {
-            LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
-        }
-#endif /* CONFIG_ZMK_BLE */
-
-        break;
+        return send_keypad_report();
     case USAGE_CONSUMER:
-        consumer_report = zmk_hid_get_consumer_report();
-#ifdef CONFIG_ZMK_USB
-        if (zmk_usb_hid_send_report((u8_t *)consumer_report,
-                                    sizeof(struct zmk_hid_consumer_report)) != 0) {
-            LOG_DBG("USB Send Failed");
-        }
-#endif /* CONFIG_ZMK_USB */
-
-#ifdef CONFIG_ZMK_BLE
-        err = zmk_hog_send_consumer_report(&consumer_report->body);
-        if (err) {
-            LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
-        }
-#endif /* CONFIG_ZMK_BLE */
-
-        break;
+        return send_consumer_report();
     default:
         LOG_ERR("Unsupported usage page %d", usage_page);
         return -ENOTSUP;
     }
+}
+
+#if IS_ENABLED(CONFIG_SETTINGS)
+
+static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb,
+                                void *cb_arg) {
+    LOG_DBG("Setting endpoint value %s", log_strdup(name));
+
+    if (settings_name_steq(name, "preferred", NULL)) {
+        if (len != sizeof(enum zmk_endpoint)) {
+            LOG_ERR("Invalid endpoint size (got %d expected %d)", len, sizeof(enum zmk_endpoint));
+            return -EINVAL;
+        }
+
+        int err = read_cb(cb_arg, &preferred_endpoint, sizeof(enum zmk_endpoint));
+        if (err <= 0) {
+            LOG_ERR("Failed to read preferred endpoint from settings (err %d)", err);
+            return err;
+        }
+
+        update_current_endpoint();
+    }
 
     return 0;
 }
+
+struct settings_handler endpoints_handler = {.name = "endpoints", .h_set = endpoints_handle_set};
+#endif /* IS_ENABLED(CONFIG_SETTINGS) */
+
+static int zmk_endpoints_init(struct device *_arg) {
+#if IS_ENABLED(CONFIG_SETTINGS)
+    settings_subsys_init();
+
+    int err = settings_register(&endpoints_handler);
+    if (err) {
+        LOG_ERR("Failed to register the endpoints settings handler (err %d)", err);
+        return err;
+    }
+
+    settings_load();
+#endif
+
+    return 0;
+}
+
+static bool is_usb_ready() {
+#if IS_ENABLED(CONFIG_ZMK_USB)
+    return zmk_usb_is_hid_ready();
+#else
+    return false;
+#endif
+}
+
+static bool is_ble_ready() {
+#if IS_ENABLED(CONFIG_ZMK_BLE)
+    return zmk_ble_active_profile_is_connected();
+#else
+    return false;
+#endif
+}
+
+static enum zmk_endpoint get_selected_endpoint() {
+    if (is_ble_ready()) {
+        if (is_usb_ready()) {
+            LOG_DBG("Both endpoints are ready. Using %d", preferred_endpoint);
+            return preferred_endpoint;
+        }
+
+        LOG_DBG("Only BLE is ready.");
+        return ZMK_ENDPOINT_BLE;
+    }
+
+    if (is_usb_ready()) {
+        LOG_DBG("Only USB is ready.");
+        return ZMK_ENDPOINT_USB;
+    }
+
+    LOG_DBG("No endpoints are ready.");
+    return DEFAULT_ENDPOINT;
+}
+
+static void disconnect_current_endpoint() {
+    zmk_hid_keypad_clear();
+    zmk_hid_consumer_clear();
+
+    zmk_endpoints_send_report(USAGE_KEYPAD);
+    zmk_endpoints_send_report(USAGE_CONSUMER);
+}
+
+static void update_current_endpoint() {
+    enum zmk_endpoint new_endpoint = get_selected_endpoint();
+
+    if (new_endpoint != current_endpoint) {
+        /* Cancel all current keypresses so keys don't stay held on the old endpoint. */
+        disconnect_current_endpoint();
+
+        current_endpoint = new_endpoint;
+        LOG_INF("Endpoint changed: %d", current_endpoint);
+    }
+}
+
+static int endpoint_listener(const struct zmk_event_header *eh) {
+    update_current_endpoint();
+    return 0;
+}
+
+ZMK_LISTENER(endpoint_listener, endpoint_listener);
+#if IS_ENABLED(CONFIG_ZMK_USB)
+ZMK_SUBSCRIPTION(endpoint_listener, usb_conn_state_changed);
+#endif
+#if IS_ENABLED(CONFIG_ZMK_BLE)
+ZMK_SUBSCRIPTION(endpoint_listener, ble_active_profile_changed);
+#endif
+
+SYS_INIT(zmk_endpoints_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
diff --git a/app/src/events/usb_conn_state_changed.c b/app/src/events/usb_conn_state_changed.c
new file mode 100644
index 00000000..d845f6d7
--- /dev/null
+++ b/app/src/events/usb_conn_state_changed.c
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <kernel.h>
+#include <zmk/events/usb-conn-state-changed.h>
+
+ZMK_EVENT_IMPL(usb_conn_state_changed);
\ No newline at end of file
diff --git a/app/src/hid.c b/app/src/hid.c
index 207b1adf..216cec7a 100644
--- a/app/src/hid.c
+++ b/app/src/hid.c
@@ -68,6 +68,8 @@ int zmk_hid_keypad_release(zmk_key code) {
     return 0;
 };
 
+void zmk_hid_keypad_clear() { memset(&kp_report.body, 0, sizeof(kp_report.body)); }
+
 int zmk_hid_consumer_press(zmk_key code) {
     TOGGLE_CONSUMER(0U, code);
     return 0;
@@ -78,6 +80,8 @@ int zmk_hid_consumer_release(zmk_key code) {
     return 0;
 };
 
+void zmk_hid_consumer_clear() { memset(&consumer_report.body, 0, sizeof(consumer_report.body)); }
+
 struct zmk_hid_keypad_report *zmk_hid_get_keypad_report() {
     return &kp_report;
 }
diff --git a/app/src/power.c b/app/src/power.c
index 73b3f123..bad54d28 100644
--- a/app/src/power.c
+++ b/app/src/power.c
@@ -23,14 +23,7 @@ static u32_t power_last_uptime;
 
 bool is_usb_power_present() {
 #ifdef CONFIG_USB
-    enum usb_dc_status_code usb_status = zmk_usb_get_status();
-    switch (usb_status) {
-    case USB_DC_DISCONNECTED:
-    case USB_DC_UNKNOWN:
-        return false;
-    default:
-        return true;
-    }
+    return zmk_usb_is_powered();
 #else
     return false;
 #endif /* CONFIG_USB */
diff --git a/app/src/usb.c b/app/src/usb.c
index 434b3d4d..d4bc2e41 100644
--- a/app/src/usb.c
+++ b/app/src/usb.c
@@ -13,6 +13,8 @@
 
 #include <zmk/hid.h>
 #include <zmk/keymap.h>
+#include <zmk/event-manager.h>
+#include <zmk/events/usb-conn-state-changed.h>
 
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
@@ -53,9 +55,34 @@ int zmk_usb_hid_send_report(const u8_t *report, size_t len) {
 
 #endif /* CONFIG_ZMK_USB */
 
+static void raise_usb_status_changed_event() {
+    struct usb_conn_state_changed *ev = new_usb_conn_state_changed();
+    ev->conn_state = zmk_usb_get_conn_state();
+
+    ZMK_EVENT_RAISE(ev);
+}
+
 enum usb_dc_status_code zmk_usb_get_status() { return usb_status; }
 
-void usb_status_cb(enum usb_dc_status_code status, const u8_t *params) { usb_status = status; };
+enum zmk_usb_conn_state zmk_usb_get_conn_state() {
+    switch (usb_status) {
+    case USB_DC_DISCONNECTED:
+    case USB_DC_UNKNOWN:
+        return ZMK_USB_CONN_NONE;
+
+    case USB_DC_ERROR:
+    case USB_DC_RESET:
+        return ZMK_USB_CONN_POWERED;
+
+    default:
+        return ZMK_USB_CONN_HID;
+    }
+}
+
+void usb_status_cb(enum usb_dc_status_code status, const u8_t *params) {
+    usb_status = status;
+    raise_usb_status_changed_event();
+};
 
 static int zmk_usb_init(struct device *_arg) {
     int usb_enable_ret;
diff --git a/docs/docs/behavior/outputs.md b/docs/docs/behavior/outputs.md
new file mode 100644
index 00000000..50afac9c
--- /dev/null
+++ b/docs/docs/behavior/outputs.md
@@ -0,0 +1,59 @@
+---
+title: Output Selection Behavior
+sidebar_label: Output Selection
+---
+
+## Summary
+
+The output behavior allows selecting whether keyboard output is sent to the
+USB or bluetooth connection when both are connected. This allows connecting a
+keyboard to USB for power but outputting to a different device over bluetooth.
+
+By default, output is sent to USB when both USB and BLE are connected.
+Once you select a different output, it will be remembered until you change it again.
+
+## Output Command Defines
+
+Output command defines are provided through the [`dt-bindings/zmk/outputs.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/outputs.h)
+header, which is added at the top of the keymap file:
+
+```
+#include <dt-bindings/zmk/outputs.h>
+```
+
+This allows you to reference the actions defined in this header:
+
+| Define              | Action                                          | Alias     |
+| ------------------- | ----------------------------------------------- | --------- |
+| `OUTPUT_USB_CMD`    | Prefer sending to USB                           | `OUT_USB` |
+| `OUTPUT_BLE_CMD`    | Prefer sending to the current bluetooth profile | `OUT_BLE` |
+| `OUTPUT_TOGGLE_CMD` | Toggle between USB and BLE                      | `OUT_TOG` |
+
+## Output Selection Behavior
+
+The output selection behavior changes the preferred output on press.
+
+### Behavior Binding
+
+- Reference: `&out`
+- Parameter #1: Command, e.g. `OUT_BLE`
+
+### Example:
+
+1. Behavior binding to prefer sending keyboard output to USB
+
+    ```
+    &out OUT_USB
+    ```
+
+1. Behavior binding to prefer sending keyboard output to the current bluetooth profile
+
+    ```
+    &out OUT_BLE
+    ```
+
+1. Behavior binding to toggle between preferring USB and BLE
+
+    ```
+    &out OUT_TOG
+    ```
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 54f6576b..c8dc79fc 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -22,6 +22,7 @@ module.exports = {
       "behavior/mod-tap",
       "behavior/reset",
       "behavior/bluetooth",
+      "behavior/outputs",
       "behavior/lighting",
       "behavior/power",
     ],