NativeDesktop

Programming model

Views, entities, and contexts, what render returns, and how the retained tree reaches a backend.

An app is a set of entities. Some of them are views: they implement Render and return an element tree. When an entity changes, it calls cx.notify(), and the framework runs render again for that view only. Everything runs in one process on the main thread, so there's no IPC layer to cross.

Example: Hello. The smallest app: a toolbar button, a count and window controls placed by coordinates. Source in examples/hello. It also runs live in the docs app.
Hello on macOS

Captured on macOS 27.0, 2026-10-22.

Hello on Linux

Captured on NixOS 26.11 (Zokor), Hyprland, 2026-10-22.

Hello on Windows

Captured on Windows 11 26H2, 2026-10-22.

Views and render

pub struct Counter {
    count: u32,
}

impl Render for Counter {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let theme = cx.theme();
        scaffold()
            .toolbar(toolbar().item(button("add", "Add one").on_click(cx.listener(
                |this, _: &ClickEvent, _window, cx| {
                    this.count += 1;
                    cx.notify();
                },
            ))))
            .content(
                v_flex()
                    .size_full()
                    .items_center()
                    .justify_center()
                    .gap_2()
                    .child(window_controls().at(point(px(16.), px(10.))))
                    .child(text(self.count.to_string()).text_size(px(48.)).tabular_nums())
                    .child(text("clicks").text_color(theme.text.secondary)),
            )
    }
}

From examples/hello/src/lib.rs, line 4.

Counter is a plain struct. impl Render gives it one method, render(&mut self, window, cx), which returns impl IntoElement. render describes how the window should look now. It doesn't change anything on screen. Keep it cheap and free of side effects, because the framework calls it whenever the view is notified.

cx is a Context<Counter>. It dereferences to App, so everything an App can do (open a window, read the theme, set a global) works through it, and it adds what is specific to this entity:

Entities

cx.new(|cx| value) builds an Entity<T>, a shared handle to a T that the app owns. Read an entity with entity.read(cx) and change it with entity.update(cx, |value, cx| ..). An update leases the value, so reading or updating the same entity from inside its own update panics. Views are entities, and so are models, which hold data and have no render. A WeakEntity returns None from update once the entity is gone, which is why callbacks use it.

pub fn app() -> Application {
    Application::new(nd::manifest!())
}

pub fn open(cx: &mut App) -> WindowHandle<Counter> {
    cx.open_window(WindowOptions::main("main").title("Hello"), |_window, cx| cx.new(|_cx| Counter { count: 0 }))
}

From examples/hello/src/lib.rs, line 35.

open_window builds the root view with a closure and returns a WindowHandle<Counter>. handle.id() is the window id, and handle.root() is the root entity. An entity isn't bound to a window, so a view can move to another window and keep its state.

Invalidation is per view

cx.notify() runs render again for that one view. A child that's itself an Entity is a boundary: the parent and the child each render only when they're notified, so a keystroke in a text editor doesn't re-render the list next to it. A change costs as much as the dirty view, not the whole window.

After render, the framework compares the new element description with the retained tree and sends the backend only what changed: create, insert, move, remove, update, and, for retained backends, a frame. Event handlers live on the retained node, and the framework swaps them without a backend call, so a new closure on every render costs nothing. Elements explains how a child is matched to its previous self.

The backend seam

The framework talks to a platform through one Backend trait. The AppKit backend, the GPUI backend, and the test backend implement it, and your app never sees which one it has.

Native controls never call into your code while the framework is applying changes. AppKit fires delegate methods inside setters and during layout, and an app entity can be mid-update at that moment. A native control pushes its events onto a per-window queue instead, and the queue drains after the transaction, outside every entity lease. That's why a handler can safely change any entity.

Tests use the same tree

TestApp is an App over a TestBackend that records the ops and exposes the semantic tree. The counter's test opens the real hello::open and clicks the button by id. For more, see the hello tutorial and the testing page.