dioxus-compose guide

Native UI, written in Rust

Author your UI with Dioxus. Let Compose draw it.

dioxus-compose is a GUI stack in which Rust code describes the interface with rsx!, components and hooks, and an ahead-of-time compiled Compose Multiplatform renderer paints it. No webview is embedded, and no JVM ships with your application.

Why it exists

This stack is aimed at applications that stay open all day, where weight is felt every hour. Web-based shells are not light: a webview brings a browser engine with it, and a bundled JRE adds 80 to 120 MB even after jlink.

The obvious answer, a pure-Rust UI framework, fails on the part that matters most for anything text-driven: text itself. Korean, Japanese and Chinese input goes through the platform IME, and an IME that is 90% right is an app you cannot use. Compose Multiplatform already has mature text, IME and widget behaviour, so this project borrows exactly that and nothing else: Rust keeps the application code, Compose becomes an interpreter for a UI tree that Rust sends it.

ConstraintConsequence
No webviewNo WKWebView, WebView2 or WebKitGTK; no Tauri, no wry.
No bundled JVMDesktop ships as a GraalVM native-image shared library; iOS is planned as Kotlin/Native.
No hand-written JNI or cinteropEvery boundary shim is generated from the Rust schema.
UI code lives in RustKotlin is an implementation detail of the renderer, not a place you write features.
Compose-quality text and IMENo code path may bypass Compose's platform text input.

What it looks like

This is the desktop_demo example from the repository, shortened. The widget names are Compose's; the attributes are their snake_case Rust spellings.

dioxus-compose/examples/desktop_demo.rs
use dioxus_compose::prelude::*;

fn app() -> Element {
    let mut messages = use_signal(Vec::<String>::new);
    let mut draft = use_signal(String::new);

    rsx! {
        Column {
            fill_max_width: true,
            Text { text: "dioxus-compose chat" }
            for message in messages() {
                Text { text: message }
            }
            TextField {
                placeholder: "Write a message",
                multiline: true,
                on_value_change: move |value| draft.set(value),
                on_key_down: move |event: KeyEvent| {
                    if event.key() == Key::Enter && !event.shift_key() {
                        // … push the draft into messages …
                        event.consume();
                    }
                }
            }
        }
    }
}

fn main() {
    dioxus_compose::launch(app);
}

Status

This is an honest table, not a roadmap dressed as a feature list. Only macOS desktop runs end to end today. Everything marked planned is designed in SPEC.md and not implemented.

PlatformStatusHow it is meant to work
macOS desktop (arm64) Works Renderer built with GraalVM native-image --shared on Liberica NIK 25 Full; the Rust binary links it. Verified 2026-09-20.
Windows / Linux desktop Planned Same native-image path. Only macOS is scripted so far; the build script refuses to run elsewhere.
iOS Planned Kotlin/Native -produce static exporting the same C symbols.
Android Planned Kotlin owns the process and the loop; Rust is a cdylib reached through generated JNI shims.
Web (wasm) Planned Kotlin owns the wasm linear memory and Rust imports it, so the batch arena is shared in place with no copying. Each boundary call goes through a generated JS forwarder, measured at about 12 ns.

Feature status

FeatureStatusNotes
Widgets: Column, Row, Box, Text, TextField, Button, Spacer, LazyColumnWorksThe whole schema, eight widgets. Anything else has to be added to it first.
Events and synchronous consumptionWorkson_click, on_value_change, on_submit, on_focus_lost, on_key_down with event.consume().
Uncontrolled TextFieldWorksEdit and composition state live in the renderer.
Kotlin protocol codegen from the Rust schemaWorksA stale Kotlin file fails the test suite.
Modifiers beyond fill_max_width / fill_max_heightPartialPadding, size, background and clickable exist in the wire schema but have no rsx! attribute yet.
LazyColumn windowing (FR-8)Host sideComponent, protocol and Host tests pass; the Kotlin interpreter still draws it as a Column and sends no RangeRequested.
AppendText streaming (FR-9)Host sideCoalesced and flushed per frame by the Host. No rsx!-level API yet; Host::append_text takes a protocol node id.
Design systems and theming (FR-13, FR-14)PlannedSpecified as Agreed, down to the wire tags, but nothing is built: no Paint, TypeRole, DesignSystem or SetTheme exists in the crate. See Design systems.

Where to go next