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.
| Constraint | Consequence |
|---|---|
| No webview | No WKWebView, WebView2 or WebKitGTK; no Tauri, no wry. |
| No bundled JVM | Desktop ships as a GraalVM native-image shared library; iOS is planned as Kotlin/Native. |
| No hand-written JNI or cinterop | Every boundary shim is generated from the Rust schema. |
| UI code lives in Rust | Kotlin is an implementation detail of the renderer, not a place you write features. |
| Compose-quality text and IME | No 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.
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.
| Platform | Status | How 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
| Feature | Status | Notes |
|---|---|---|
Widgets: Column, Row, Box, Text, TextField, Button, Spacer, LazyColumn | Works | The whole schema, eight widgets. Anything else has to be added to it first. |
| Events and synchronous consumption | Works | on_click, on_value_change, on_submit, on_focus_lost, on_key_down with event.consume(). |
Uncontrolled TextField | Works | Edit and composition state live in the renderer. |
| Kotlin protocol codegen from the Rust schema | Works | A stale Kotlin file fails the test suite. |
Modifiers beyond fill_max_width / fill_max_height | Partial | Padding, size, background and clickable exist in the wire schema but have no rsx! attribute yet. |
LazyColumn windowing (FR-8) | Host side | Component, protocol and Host tests pass; the Kotlin interpreter still draws it as a Column and sends no RangeRequested. |
AppendText streaming (FR-9) | Host side | Coalesced 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) | Planned | Specified 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
Getting started
Install Liberica NIK, build the renderer library, run the desktop demo.
Writing UI
The widget set, event handlers, key consumption, and the uncontrolled text field.
Lists & streaming
Windowed lists and token streaming: what the Host does today, and what the renderer still owes.
Design systems
Colour, type, shape and spacing roles, and the two theme modes. Specified, not yet built.
Architecture
The Host/Renderer split, the C ABI, the wire protocol and the frame model.