How Does Event Handling Work Across Different Dioxus Renderers?
Dioxus unifies event handling across all platforms by funneling native UI events through a central Runtime as synthetic, typed objects, allowing Web, Desktop, Native, and LiveView renderers to share identical component code while only differing in their native event translation layer.
Dioxus is a Rust library for building cross-platform user interfaces that target web browsers, desktop applications, mobile devices, and server-side live views. Understanding how event handling works across different Dioxus renderers is essential for writing platform-agnostic components that respond consistently to user interactions regardless of the target environment.
The Core Event Architecture
At the heart of Dioxus lies a centralized Runtime that manages event dispatch for every supported platform. When a user interacts with your application, the specific renderer (Web, Desktop, Native, or LiveView) captures the native event and translates it into a synthetic event—a typed object wrapped in Event<dyn Any>.
The runtime exposes the primary dispatch method in packages/core/src/runtime.rs:
Runtime::handle_event(name, event, element_id)
This method receives the event name (e.g., "click"), the synthetic event payload, and the target element's ID. According to the source code in packages/core/src/events.rs, the runtime stores event listeners within the Virtual DOM and executes them in bubbling order when the event_bubbles flag permits propagation.
Renderer-Specific Event Bridges
Each renderer implements a thin translation layer that converts platform-native events into Dioxus's synthetic format before delegating to the shared runtime.
Web Renderer
The Web renderer bridges browser DOM events to the Dioxus runtime. In packages/web/src/mutations.rs, the create_event_listener method registers native addEventListener callbacks on DOM elements:
fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
self.websys_dom
.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8);
}
When the browser fires a native event, the Web renderer constructs a Synthetic<ClickEvent> (or appropriate type from packages/web/src/events/*.rs) and invokes runtime.handle_event. The renderer also manages the mounted event queue via flush_queued_mounted_events, ensuring onmounted lifecycle hooks execute exactly once after DOM node creation.
Desktop Renderer
For desktop applications, Dioxus embeds a WebView that communicates with the Rust backend through a specialized protocol. The Desktop renderer, implemented in packages/desktop/src/webview.rs, intercepts events from the embedded WebView and forwards them to the core runtime. This allows desktop applications to use the same event handling patterns as web applications while operating within a native window context.
Native Renderer
The Native renderer targets mobile and embedded platforms without relying on WebView technology. In packages/native-dom/src/dioxus_document.rs, the renderer attaches native OS callbacks directly to platform-specific UI elements. These callbacks translate touch, gesture, and input events into the synthetic Dioxus format before calling the shared runtime methods.
LiveView Renderer
LiveView enables server-rendered applications where user interactions travel over WebSocket connections. The LiveView renderer in packages/liveview/src/pool.rs receives serialized events from the client, reconstructs them as synthetic events on the server, and dispatches them through the same Runtime::handle_event path used by client-side renderers.
Event Bubbling and Propagation Control
Dioxus respects standard DOM event propagation semantics through the event_bubbles function defined in dioxus_core_types. When Runtime::handle_event processes an event, it checks this function to determine whether the event should propagate upward through the component tree.
The runtime executes listeners in bubbling order—from the target element up through its ancestors—respecting any propagation flags that listeners may set to stop further bubbling. This behavior remains consistent across all renderers because the bubbling logic lives entirely within the platform-agnostic core runtime rather than in renderer-specific code.
The Mounted Event Lifecycle
Beyond user interactions, Dioxus handles component lifecycle events through a special "mounted" event system. When a component first renders, the runtime queues a mounted event for each element. After the renderer creates the corresponding native UI nodes, it calls flush_queued_mounted_events to fire these queued events exactly once.
This mechanism ensures that onmounted callbacks execute only after the element exists in the native view hierarchy, whether that hierarchy consists of browser DOM nodes, Desktop WebView elements, or Native platform views.
Practical Implementation Examples
The following examples demonstrate how Dioxus event handling works in practice across different renderers.
Standard component usage (works identically on every platform):
#[component]
fn Counter() -> Element {
let mut count = use_signal(|| 0);
rsx! {
button {
onclick: move |_| count += 1,
"Clicked {count} times"
}
}
}
The onclick attribute registers a listener that the runtime invokes upon receiving a "click" event for that button's element ID.
Manual event dispatch (useful for testing or programmatic control):
use dioxus::prelude::*;
use dioxus_core::Event;
fn trigger_custom_event(dom: &VirtualDom) {
// Create a synthetic event payload (any type implementing Any)
let payload = Rc::new("my payload".to_string()) as Rc<dyn Any>;
let event = Event::new(payload);
// Dispatch to element with id 42
dom.runtime().handle_event("my_event", event, ElementId(42));
}
Because all renderers ultimately call runtime.handle_event, this approach works regardless of whether the application runs in a browser, desktop window, or native mobile shell.
Summary
- Dioxus treats all UI events as synthetic, typed objects processed through a central
Runtimeshared by every renderer. - Each renderer (Web, Desktop, Native, LiveView) translates native platform events into Dioxus's format before calling
Runtime::handle_eventinpackages/core/src/runtime.rs. - Event bubbling behavior is controlled by the
event_bubblesfunction and enforced consistently across platforms during listener execution. - Mounted events are queued during rendering and flushed once native nodes exist, enabling reliable
onmountedlifecycle hooks. - Component code remains identical across renderers because event dispatch logic lives in the platform-agnostic core rather than renderer-specific implementations.
Frequently Asked Questions
Do I need to write different event handling code for Web versus Desktop renderers?
No. Dioxus abstracts platform differences through synthetic events. Your component code using attributes like onclick or oninput works identically across Web, Desktop, Native, and LiveView renderers because each renderer translates native events into the same format before reaching your application logic.
How does Dioxus handle events that don't bubble, like focus or blur?
The runtime checks the event_bubbles function (defined in dioxus_core_types) before propagating events. For non-bubbling events, the runtime executes only the listeners attached to the target element without traversing up the component tree, matching standard browser behavior while maintaining cross-platform consistency.
Can I manually trigger events programmatically in Dioxus?
Yes. You can construct an Event object with any payload implementing Any and dispatch it through Runtime::handle_event by accessing the virtual DOM's runtime. This works universally because all renderers rely on the same core runtime method for event processing.
Where does the event bubbling logic live in the codebase?
Event bubbling logic resides in packages/core/src/runtime.rs within the Runtime::handle_event implementation. The runtime looks up registered listeners for the target element and executes them in bubbling order, checking propagation flags after each invocation to determine whether to continue up the tree.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →