diff --git a/include/components/bar.hpp b/include/components/bar.hpp
index 4e6a4a13..7ce477e6 100644
--- a/include/components/bar.hpp
+++ b/include/components/bar.hpp
@@ -28,7 +28,7 @@
 
 LEMONBUDDY_NS
 
-class bar : public xpp::event::sink<evt::button_press, evt::expose> {
+class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify> {
  public:
   /**
    * Construct bar
@@ -230,7 +230,7 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose> {
       XCB_AUX_ADD_PARAM(&mask, &params, border_pixel, m_bar.background.value());
       XCB_AUX_ADD_PARAM(&mask, &params, colormap, m_colormap);
       XCB_AUX_ADD_PARAM(&mask, &params, override_redirect, m_bar.dock);
-      XCB_AUX_ADD_PARAM(&mask, &params, event_mask, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS);
+      XCB_AUX_ADD_PARAM(&mask, &params, event_mask, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS);
       // clang-format on
       m_window.create_checked(m_bar.x, m_bar.y, m_bar.width, m_bar.height, mask, &params);
     }
@@ -644,6 +644,41 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose> {
     flush();
   }  // }}}
 
+  /**
+   * Event handler for XCB_PROPERTY_NOTIFY events
+   *
+   * Used to emit events whenever the bar window's
+   * visibility gets changes. This allows us to toggle the
+   * state of the tray container even though the tray
+   * window restacking failed.
+   *
+   * This is used as a fallback for tedious WM's, like i3.
+   *
+   * Some might call it a dirty hack, others a crappy
+   * solution... I choose to call it a masterpiece! Plus
+   * it's not really any overhead worth talking about.
+   */
+  void handle(const evt::property_notify& evt) {  // {{{
+    if (evt->window == m_window && evt->atom == WM_STATE) {
+      if (g_signals::bar::visibility_change.empty()) {
+        return;
+      }
+      try {
+        auto attr = m_connection.get_window_attributes(m_window);
+        if (attr->map_state == XCB_MAP_STATE_VIEWABLE)
+          g_signals::bar::visibility_change.emit(true);
+        else if (attr->map_state == XCB_MAP_STATE_UNVIEWABLE)
+          g_signals::bar::visibility_change.emit(false);
+        else if (attr->map_state == XCB_MAP_STATE_UNMAPPED)
+          g_signals::bar::visibility_change.emit(false);
+        else
+          g_signals::bar::visibility_change.emit(true);
+      } catch (const std::exception& err) {
+        m_log.warn("Failed to emit bar window's visibility change event");
+      }
+    }
+  }  // }}}
+
  protected:
   /**
    * Handle alignment update
diff --git a/include/components/x11/tray.hpp b/include/components/x11/tray.hpp
index d9705e5c..67e7deb8 100644
--- a/include/components/x11/tray.hpp
+++ b/include/components/x11/tray.hpp
@@ -145,6 +145,13 @@ class traymanager
       m_sinkattached = true;
     }
 
+    // Listen for visibility change events on the bar window
+    if (!m_restacked) {
+      g_signals::bar::visibility_change.connect(this, &traymanager::bar_visibility_change);
+    }
+
+    // Attempt to get control of the systray selection then
+    // notify clients waiting for a manager.
     acquire_selection();
     notify_clients();
 
@@ -185,6 +192,10 @@ class traymanager
       m_sinkattached = false;
     }
 
+    if (!m_restacked) {
+      g_signals::bar::visibility_change.disconnect(this, &traymanager::bar_visibility_change);
+    }
+
     // Dismiss all clients by reparenting them to the root window
     m_logger.trace("tray: Unembed clients");
     for (auto&& client : m_clients) {
@@ -211,6 +222,12 @@ class traymanager
    * reposition embedded clients
    */
   void reconfigure() {
+    // Ignore reconfigure requests when the
+    // tray window is in the pseudo-hidden state
+    if (m_hidden) {
+      return;
+    }
+
     uint32_t width = 0;
     uint16_t mapped_clients = 0;
 
@@ -266,6 +283,29 @@ class traymanager
   }
 
  protected:
+  /**
+   * Signal handler connected to the bar window's visibility change signal.
+   * This is used as a fallback in case the window restacking fails. It will
+   * toggle the tray window whenever the visibility of the bar window changes.
+   */
+  void bar_visibility_change(bool state) {
+    try {
+      // Ignore unchanged states
+      if (m_hidden == !state)
+        return;
+
+      // Update the psuedo-state
+      m_hidden = !state;
+
+      if (state && !m_mapped)
+        m_connection.map_window_checked(m_tray);
+      else if (!state && m_mapped)
+        m_connection.unmap_window_checked(m_tray);
+    } catch (const std::exception& err) {
+      m_logger.warn("Failed to un-/map the tray window (%s)", err.what());
+    }
+  }
+
   /**
    * Calculate the tray window's horizontal position
    */
@@ -328,6 +368,7 @@ class traymanager
         const uint32_t value_list[2]{m_settings.sibling, XCB_STACK_MODE_ABOVE};
         m_connection.configure_window_checked(m_tray, value_mask, value_list);
         m_connection.flush();
+        m_restacked = true;
       }
     } catch (const std::exception& err) {
       auto id = m_connection.id(m_settings.sibling);
@@ -725,9 +766,12 @@ class traymanager
 
   stateflag m_activated{false};
   stateflag m_mapped{false};
+  stateflag m_hidden{false};
   stateflag m_sinkattached{false};
 
   thread m_notifythread;
+
+  bool m_restacked = false;
 };
 
 // }}}