Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DioxusLabs/dioxus/llms.txt
Use this file to discover all available pages before exploring further.
The Dioxus desktop renderer uses Wry (WebView) and Tao (windowing) to create native desktop applications. Your app runs with native performance while rendering HTML/CSS through the platform’s native webview.
Quick Start
Add the desktop dependency:
[dependencies]
dioxus = { version = "0.6", features = ["desktop"] }
Create a simple desktop app:
use dioxus::prelude::*;
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
let mut count = use_signal(|| 0);
rsx! {
h1 { "Desktop App" }
button { onclick: move |_| count += 1, "Count: {count}" }
}
}
Run your app:
dx serve --platform desktop
Or build a release:
dx build --platform desktop --release
Architecture
Wry and Tao
Dioxus Desktop is built on:
- Wry: Cross-platform WebView library
- Tao: Cross-platform window creation and management
- Native WebView: Uses the system’s browser engine
- Windows: WebView2 (Chromium)
- macOS: WKWebView (Safari)
- Linux: WebKitGTK
IPC Communication
Desktop apps use Inter-Process Communication (IPC) to bridge Rust and the webview:
Browser Event → JavaScript → window.postMessage()
→ Wry intercepts request
→ Extract dioxus-data header (base64 JSON)
→ IpcMessage { method, params }
→ Handle: UserEvent, Query, BrowserOpen, Initialize
Configuration
Customize your desktop app with Config:
use dioxus::prelude::*;
use dioxus_desktop::{Config, WindowBuilder, LogicalSize};
fn main() {
dioxus::LaunchBuilder::new()
.with_cfg(
Config::new()
.with_window(
WindowBuilder::new()
.with_title("My App")
.with_inner_size(LogicalSize::new(800.0, 600.0))
.with_resizable(true)
)
)
.launch(App);
}
Window Configuration
use dioxus_desktop::{Config, WindowBuilder, tao::dpi::LogicalSize};
Config::new()
.with_window(
WindowBuilder::new()
.with_title("My Application")
.with_inner_size(LogicalSize::new(1200.0, 800.0))
.with_min_inner_size(LogicalSize::new(400.0, 300.0))
.with_resizable(true)
.with_maximized(false)
.with_decorations(true)
.with_transparent(false)
.with_always_on_top(false)
)
Application Icon
use dioxus_desktop::{Config, tao::window::Icon};
let icon_bytes = include_bytes!("../assets/icon.png");
let icon = Icon::from_rgba(
icon_bytes.to_vec(),
256, // width
256 // height
).unwrap();
Config::new().with_icon(icon)
Background Color
Set the window background to prevent flashing:
Config::new()
.with_background_color((255, 255, 255, 255)) // RGBA
Window Management
Access the window through the use_window hook:
use dioxus::prelude::*;
use dioxus_desktop::{use_window, LogicalSize};
#[component]
fn WindowControls() -> Element {
let window = use_window();
rsx! {
button {
onclick: move |_| {
window.set_title("New Title");
},
"Change Title"
}
button {
onclick: move |_| {
window.set_inner_size(LogicalSize::new(1024.0, 768.0));
},
"Resize Window"
}
button {
onclick: move |_| {
window.set_minimized(true);
},
"Minimize"
}
button {
onclick: move |_| {
window.set_fullscreen(true);
},
"Fullscreen"
}
}
}
Multiple Windows
Create additional windows dynamically:
use dioxus::prelude::*;
use dioxus_desktop::{use_window, WindowBuilder};
#[component]
fn MultiWindow() -> Element {
let window = use_window();
let open_new = move |_| {
window.new_window(
WindowBuilder::new()
.with_title("New Window")
.build()
.unwrap(),
NewWindow
);
};
rsx! {
button { onclick: open_new, "Open New Window" }
}
}
#[component]
fn NewWindow() -> Element {
rsx! {
h1 { "This is a new window!" }
}
}
Add native menu bars using the muda crate:
use dioxus::prelude::*;
use dioxus_desktop::{Config, muda::{Menu, Submenu, MenuItem}};
fn main() {
let menu = Menu::new();
let file_menu = Submenu::new("File", true);
file_menu.append(&MenuItem::new("Open", true, None)).unwrap();
file_menu.append(&MenuItem::new("Save", true, None)).unwrap();
file_menu.append(&MenuItem::new("Exit", true, None)).unwrap();
menu.append(&file_menu).unwrap();
dioxus::LaunchBuilder::new()
.with_cfg(Config::new().with_menu(menu))
.launch(App);
}
Handling Menu Events
use dioxus::prelude::*;
use dioxus_desktop::{use_muda_event_handler, muda::MenuEvent};
#[component]
fn App() -> Element {
use_muda_event_handler(|event: &MenuEvent| {
match event.id().0.as_str() {
"open" => println!("Open clicked"),
"save" => println!("Save clicked"),
"exit" => std::process::exit(0),
_ => {}
}
});
rsx! {
h1 { "Menu Example" }
}
}
System Tray
Add a system tray icon:
use dioxus_desktop::trayicon::{TrayIconBuilder, MenuBuilder};
let icon_bytes = include_bytes!("../assets/tray-icon.png");
let tray = TrayIconBuilder::new()
.with_icon(icon_bytes)
.with_tooltip("My App")
.with_menu(Box::new(MenuBuilder::new()))
.build()
.unwrap();
Custom Protocols
Handle custom URL schemes:
use dioxus_desktop::Config;
use wry::http::{Request, Response};
use std::borrow::Cow;
Config::new()
.with_custom_protocol("myapp", |_webview_id, request: Request<Vec<u8>>| {
let path = request.uri().path();
// Load resource based on path
let content = match path {
"/data" => b"Custom data".to_vec(),
_ => b"Not found".to_vec(),
};
Response::builder()
.status(200)
.header("Content-Type", "text/plain")
.body(Cow::from(content))
.unwrap()
})
Async Protocol Handlers
use dioxus_desktop::Config;
use wry::http::Response;
use std::borrow::Cow;
Config::new()
.with_asynchronous_custom_protocol("asset", |_webview_id, request, responder| {
tokio::spawn(async move {
// Async file loading, database queries, etc.
let data = load_data_async().await;
responder.respond(
Response::builder()
.status(200)
.body(Cow::Borrowed(&data))
.unwrap()
);
});
})
File Dialogs
use dioxus::prelude::*;
use dioxus_desktop::use_window;
#[component]
fn FileDialog() -> Element {
let window = use_window();
let mut file_path = use_signal(|| String::new());
let open_file = move |_| {
spawn(async move {
if let Some(path) = window.open_file_dialog(
"Open File",
None,
vec![("Text Files", &["txt"]), ("All Files", &["*"])]
).await {
file_path.set(path.display().to_string());
}
});
};
rsx! {
button { onclick: open_file, "Open File" }
p { "Selected: {file_path}" }
}
}
Global Shortcuts
Register system-wide keyboard shortcuts:
use dioxus::prelude::*;
use dioxus_desktop::use_global_shortcut;
#[component]
fn App() -> Element {
let mut triggered = use_signal(|| 0);
use_global_shortcut("Ctrl+Shift+K", move |state| {
if state.is_pressed() {
triggered += 1;
}
}).ok();
rsx! {
h1 { "Press Ctrl+Shift+K" }
p { "Triggered {triggered} times" }
}
}
JavaScript Evaluation
Execute JavaScript in the webview:
use dioxus::prelude::*;
use dioxus_desktop::use_window;
#[component]
fn JsEval() -> Element {
let window = use_window();
let run_js = move |_| {
spawn(async move {
let result = window.eval(
"document.title = 'Changed by JS'; return 42;"
).await;
println!("Result: {:?}", result);
});
};
rsx! {
button { onclick: run_js, "Run JavaScript" }
}
}
Use conditional compilation for platform-specific features:
#[cfg(target_os = "windows")]
fn windows_specific() {
// Windows-only code
}
#[cfg(target_os = "macos")]
fn macos_specific() {
// macOS-only code
}
#[cfg(target_os = "linux")]
fn linux_specific() {
// Linux-only code
}
Packaging and Distribution
Bundle Your App
dx bundle --platform desktop --release
This creates platform-specific packages:
- Windows:
.exe and optionally .msi
- macOS:
.app bundle and .dmg
- Linux:
.AppImage, .deb, or .rpm
Code Signing
Configure code signing in Dioxus.toml:
[bundle]
identifier = "com.example.myapp"
version = "1.0.0"
[bundle.macos]
signing_identity = "Developer ID Application: Your Name"
[bundle.windows]
digest_algorithm = "sha256"
- Minimize IPC: Batch operations to reduce message passing
- Use Native Rendering: Leverage CSS and GPU acceleration
- Optimize Bundle Size: Use
--release and strip symbols
- Preload Resources: Load assets at startup
Next Steps