# FUI-RS - retained Rust UI for native desktop and WebAssembly
FUI-RS is the retained-mode Rust UI SDK for EffinDom. One application model
runs as a real native macOS, Windows, or Linux desktop application and in the
browser through WebAssembly. Native applications embed neither Chromium nor a
system WebView.
The SDK provides retained controls, layout nodes, text input, overlays, custom
drawing, host services, workers, accessibility semantics, routing support, and
application lifecycle macros.
The Cargo package is named `fui-rs`, while application code imports its
library as `fui`:
```bash
cargo add fui-rs --rename fui
```
## Quickstart
Create a native, web, or universal application with `cargo-fui`:
```bash
# Install stable Rust and Cargo once
cargo install --locked cargo-fui
cargo fui new my-app --target universal
cd my-app
cargo fui dev
```
Choose `native` for desktop only, `web` for browser only, or `universal` for a
shared retained UI with explicit native and web adapters. Native-only projects
do not require Node.js. `cargo fui build --release` creates optimised output and
`cargo fui package` emits DMG, MSIX, or AppImage packages.
For a browser-only routed application with one independently built WASM module
per route, use the npm scaffolder:
```bash
npx @effindomv2/create-fui-rs-app my-routed-app -- --template routed
cd my-routed-app
npm install
npm run dev
```
Install [Binaryen](https://github.com/WebAssembly/binaryen) to make release
builds run `wasm-opt`. Development builds do not require it.
Application setup, retained-mode guidance, and entrypoint examples are covered
in the [FUI-RS developer quickstart](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/QUICKSTART.md).
## Minimal app
```rust
use fui::prelude::*;
fn build_page() -> FlexBox {
ui! {
column().fill_size().padding(24.0, 24.0, 24.0, 24.0) {
text("Hello from Rust"),
button("Click me").on_click(|_| logger::info("App", "clicked")),
}
}
}
fui_app!(FlexBox, build_page);
```
## Rich text
Use `rich_text!` to create retained rich text without manually constructing a
span vector. String literals become spans, braced expressions provide dynamic
text, and `span => expression` accepts an existing `RichTextSpan`:
```rust
let value = 42;
let suffix = span("!").underline();
let label = rich_text![
"Current value: ".italic(),
{ format!("{value}") }.bold().text_color(rgb(0x3a, 0xc5, 0x6c)),
span => suffix,
]
.font_size(18.0);
```
## SDK docs
- [SDK docs index](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/SDK_INDEX.md)
- [API reference](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/API_REFERENCE.md)
- [Controls and nodes](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/CONTROLS_AND_NODES.md)
- [Events and callbacks](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/EVENTS_AND_CALLBACKS.md)
- [Text input reference](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/TEXT_INPUT_REFERENCE.md)
- [Forms and autofill](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/FORMS_AND_AUTOFILL.md)
- [Theming and style matrix](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/THEMING_STYLE_MATRIX.md)
## Contributing to FUI-RS
The commands above are for developers building applications with the published
FUI-RS SDK. Contributors working on the SDK, EffinDom runtime, browser bridge,
or repository demos should follow the
[FUI-RS contributor quickstart](https://github.com/zion-sati/fui-rs/blob/main/docs/v2/fui-rs/CONTRIBUTOR_QUICKSTART.md).
It covers the standalone repository toolchain, SDK build, lint, and test lanes.
## What is included
| Retained app lifecycle macros | Available |
| `ui!` mixed child tree macro | Available |
| `fui_component!` retained component delegation | Available |
| Flex/Grid layout nodes | Available |
| Text, rich text, image, SVG | Available |
| Buttons, toggles, slider, dropdown, combobox | Available |
| TextInput/TextArea | Available |
| Context menu, popup, dialog, tooltip | Available |
| Selection, mobile text handles, context toolbar | Available |
| ScrollView, ScrollBox, VirtualList | Available |
| Custom drawing and text layouts | Available |
| Native and browser platform adapters | Available |
| Browser file/fetch/worker bridges | Available |
| Host services/events generator support | Available |
## Recycled virtual-list rows
`VirtualList` creates a fixed retained row pool. Use `item_template` once to
construct typed row state, then update that state from `on_bind_item` whenever a
pool slot is assigned a new item index:
```rust
struct ContactRow {
name: TextNode,
}
let contacts = virtual_list(10_000, 28.0)
.item_template(|container| {
let name = text("");
container.child(&name);
ContactRow { name }
});
});
```
The template is not rerun while scrolling. Do not key recycled rows by pointer
or create controls inside `on_bind_item`.
## Scrollbar styling
Apply common scrollbar chrome without leaving fluent `ScrollBox` construction:
```rust
let content = scroll_box().scrollbar_style(
ScrollBarStyle::new()
.track_width(10.0)
.thumb_width(7.0)
.thumb_corner_radius(3.5),
);
```
Use `vertical_scrollbar()` or `horizontal_scrollbar()` afterward for an
axis-specific override.
## Host-event lifetime
Generated `on_*` host-event functions return `HostEventSubscription`. Retain
the guard for exactly as long as the handler should remain active; dropping it
unsubscribes automatically. Replacing a handler is generation-safe, so dropping
an older guard cannot remove its replacement.
## Worker entrypoints
Enable the `worker-runtime` feature, implement `Default + WorkerJob`, and let
the SDK emit resumable entries plus the shared callback-buffer ABI:
```rust
use fui::prelude::*;
#[derive(Default)]
struct PrimeJob {
state: WorkerJobState,
}
impl WorkerJob for PrimeJob {
fn state(&mut self) -> &mut WorkerJobState { &mut self.state }
fn run(&mut self) { self.complete("done"); }
}
fui_worker!(primeWorker => PrimeJob);
```
## Architecture
FUI-RS builds retained Rust UI objects against the shared EffinDom runtime.
Native hosts execute that runtime directly through platform adapters. On the
web, Rust app WASM and the UI runtime WASM are separate modules; strings and
command data cross the browser bridge through explicit UTF-8/runtime ABI calls.
Retained controls are cheap clone handles. Cloning a control gives another Rust
handle to the same retained UI object.
FUI-RS maps retained inheritance to capability traits. `Node` supplies the
universal retained/event surface; FlexBox-derived visuals additionally expose
`LayoutSurface`, `BoxStyleSurface`, `FlexLayoutSurface`, and
`ChildContainerSurface`. `TextSurface` covers `Text` and `RichText`, while
`TextEditorSurface` covers `TextInput` and `TextArea`.
Use `on_pointer_click(...)` for raw routed pointer input. Use
`on_pointer_double_click(...)` and `on_pointer_triple_click(...)` for exact raw
multi-click gestures. `Button`, `Checkbox`, `RadioButton`, and `Switch` expose
count-free `on_click(...)` semantic activation for supported pointer and
keyboard input.
## Project status
FUI-RS is feature-rich early access. Its retained SDK, web and native hosts,
controls, text editing, accessibility projection, custom drawing, and packaging
workflow are usable today, but public APIs remain pre-1.0 and may change
incompatibly.
Current platform limits:
- Linux AT-SPI support is implemented; validation across the wider desktop and screen-reader ecosystem remains early.
- iOS and Android are not currently supported.
- Browser-native find-on-page cannot be reproduced perfectly for all mirrored,
hidden, and virtualised content.
- The third-party control and integration ecosystem is new.
Try the [live demo](https://fui-rs-demo.effindom.dev/), then open a discussion
or issue if a real application is blocked by a missing capability.
## License
AGPL-3.0-only, or commercial license. See [COMMERCIAL.md](COMMERCIAL.md).