NativeDesktop

Elements

The element vocabulary: div, flex helpers, text, styling, layout, identity, and children.

render returns elements. An element is a description rather than a widget. It says what should exist, and the reconciler decides what to create, keep, or remove. use nd::prelude::*; brings the whole vocabulary into scope.

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.

The building blocks

Add one child with .child(x) and many with .children(iter). Anything that implements IntoElement can be a child, and so can an Entity<V> of a view, which renders on its own.

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.

Styling

Elements take GPUI's Styled methods: size_full(), gap_2(), px_3(), items_center(), justify_center(), flex_1(), min_h_0(), text_size(px(48.)), text_color(..), and others. Each method writes one field of the element's style. Text has a few methods of its own, such as tabular_nums() for digits that don't shift as a count changes.

Take colors, spacing, and type sizes from cx.theme(): theme.text.secondary, theme.spacing.content_margin, theme.type_scale.title. A literal color fails nd check. The tokens follow the platform's light and dark appearance and a matugen palette. For the full list, see the theming page.

Layout

Layout is flexbox. Taffy computes it with the same style mapping on every backend. A v_flex stacks children from top to bottom, .flex_1() gives a child the remaining space, and .min_w_0() lets a child shrink below its content width. Native controls report their own size (measure), so a button keeps the size the OS gives it.

Some containers lay out their own children: a native list's rows, a split view's panes, a toolbar's items, and a scroll view's document. Those are layout boundaries. The container tells the child how much room it has, the framework lays out inside, and the child's height or intrinsic size goes back to the container.

Identity

The reconciler matches each new element to a retained node by identity:

Location-based identity makes .when(..) safe. A conditional child leaves no hole in the child list, so when the condition flips, a sibling stays the same native node and keeps its focus, text selection, and scroll position.

Some(tab) => v_flex()
    .size_full()
    .when(loading, |d| d.child(progress(Some(fraction as f32)).id("progress").w_full()))
    .child(div().flex_1().w_full().child(self.page_view(tab, cx)))
    .when(!self.status.is_empty(), |d| {
        d.child(text(self.status.clone()).id("status").text_color(secondary).p(margin))
    })
    .into_any_element(),

From examples/browser/src/lib.rs, line 329.

Here, the progress bar appears and disappears without touching the page view below it. Give an explicit id to anything stateful that you create in a loop or a helper function: text fields, lists, tables, web views. An explicit id is also the element's test id, which is how click(cx, window, "add") finds a node.