| name | levien-native-ui-mastery |
| description | Build native UIs in the style of Raph Levien, architect of Druid, Xilem, and Vello. Emphasizes declarative reactive architecture, synchronized tree transformations, GPU-accelerated rendering, and idiomatic Rust patterns. Use when designing responsive, beautiful native UIs or 2D graphics systems. |
| tags | gui, native-ui, rendering, gpu, graphics, 2d, vector, canvas, widget, reactive, layout, vello, druid |
Raph Levien Style Guide
Overview
Raph Levien is a Principal Software Engineer at Canva (formerly Google Fonts) and the architect of the Linebender ecosystem: Druid, Xilem, Vello, Piet, and Kurbo. He has spent decades at the intersection of 2D graphics, UI architecture, and typography. His blog "raphlinus.github.io" is the canonical source for modern thinking about native UI in Rust.
Core Philosophy
"Architectures that work well in other languages generally don't adapt well to Rust, mostly because they rely on shared mutable state."
"Hidden inside of every UI framework is some kind of incrementalization framework."
"The end-to-end transformation is so complicated it would be very difficult to express directly. So it's best to break it down into smaller chunks, stitched together in a pipeline."
Levien sees UI as a pipeline of tree transformations. The view tree describes intent, the widget tree retains state, and the render tree produces pixels. Fighting Rust's ownership model means your architecture is wrong—find one that works with the language.
Design Principles
-
Declarative Over Imperative: UI should describe what, not how. Application logic produces a view tree; the framework handles the rest.
-
Synchronized Trees: View tree (ephemeral, typed) → Widget tree (retained, stateful) → Render tree (layout, paint). Each stage has clear responsibilities.
-
Incremental by Default: Memoize aggressively. Diff sparsely. Fine-grained change propagation beats wholesale re-rendering.
-
Statically Typed, Ergonomically Used: Leverage Rust's type system to catch errors at compile time, but don't burden the developer with excessive annotations.
-
GPU-First Rendering: The CPU describes the scene; the GPU does the work. Compute shaders can handle parsing, flattening, and rasterization.
-
Composition via Adapt Nodes: Components own a slice of state, not the global state. Adapt nodes translate between parent and child state types.
-
Accessibility Is Architecture: Screen reader support requires retained structure and stable identity. This is not an afterthought—it shapes the design.
-
Performance Is Research: Willing to solve hard problems (Euler spirals, parallel curves, GPU compute pipelines) rather than accept mediocre solutions.
When Building UI
Always
- Model UI as a pipeline of tree transformations
- Use declarative view descriptions that produce typed trees
- Design for incremental updates from the start
- Provide stable identity for widgets (id paths)
- Consider accessibility requirements early—they affect architecture
- Separate view logic (ephemeral) from widget state (retained)
- Route events through the tree with mutable access at each stage
Never
- Rely on shared mutable state for UI coordination
- Use
Rc<RefCell<T>> as a first resort—it's a sign of architectural mismatch
- Assume immediate mode can handle complex UI (accessibility, virtualized scroll)
- Create explicit message types for every interaction (Elm-style verbosity)
- Couple rendering tightly to the CPU—GPUs are massively parallel
- Ignore the borrow checker—restructure instead
Prefer
- View trees over imperative widget construction
- Adapt nodes over global message dispatch
- Id-path event routing over callback spaghetti
- Retained widget trees over pure immediate mode
- GPU compute shaders over CPU rendering loops
- Sparse collection diffing over full re-renders
- Typed erasure escape hatches (
AnyView) over runtime type chaos
Architecture Patterns
The Synchronized Tree Model
trait View {
type State;
type Widget;
fn build(&self, cx: &mut Cx) -> (Self::State, Self::Widget);
fn rebuild(&self, cx: &mut Cx, state: &mut Self::State, widget: &mut Self::Widget);
fn event(&self, state: &mut Self::State, event: &Event) -> EventResult;
}
Adapt Nodes for Composition
struct AppState {
user: UserState,
settings: SettingsState,
}
fn settings_panel(state: &mut AppState) -> impl View {
}
fn app_view(state: &mut AppState) -> impl View {
VStack::new((
Adapt::new(
|state: &mut AppState, thunk| {
thunk.call(&mut state.settings)
},
settings_panel,
),
Adapt::new(
|state: &mut AppState, thunk| thunk.call(&mut state.user),
user_panel,
),
))
}
fn settings_panel(state: &mut SettingsState) -> impl View {
Toggle::new(, & state.dark_mode)
}
Id-Path Event Routing
struct IdPath(Vec<Id>);
impl View for Button {
fn event(&self, state: &mut Self::State, id_path: &IdPath, event: &Event) -> EventResult {
if id_path.is_empty() && matches!(event, Event::Click) {
(self.on_click)(state);
EventResult::Handled
} else {
EventResult::Ignored
}
}
}
Memoization for Incremental Updates
fn item_list(items: &[Item]) -> impl View {
VStack::new(
items.iter().map(|item| {
Memoize::new(
item.id,
item.clone(),
|item| item_row(item),
)
})
)
}
GPU Scene Description
struct Scene {
encoding: Vec<u8>,
}
impl Scene {
fn fill(&mut self, path: &Path, brush: &Brush) {
self.encoding.extend(encode_fill(path, brush));
}
fn stroke(&mut self, path: &Path, style: &Stroke, brush: &Brush) {
self.encoding.extend(encode_stroke(path, style, brush));
}
fn push_transform(&mut self, transform: Affine) {
self.encoding.extend(encode_transform(transform));
}
}
Sparse Collection Diffing
use std::sync::Arc;
struct ImList<T> {
root: Option<Arc<Node<T>>>,
}
impl<T: Clone + Eq> ImList<T> {
fn diff(&self, other: &Self) -> CollectionDiff<T> {
diff_trees(&self.root, &other.root)
}
}
fn list_view(items: &ImList<Item>) -> impl View {
VirtualList::new(items, |item| item_row(item))
}
Mental Model
Levien approaches UI by asking:
- What trees are involved? — View, widget, render, draw—each has a role
- How does state flow? — Props down, events up, through Adapt nodes
- Where is the incrementalization? — What can be memoized? What must be diffed?
- Can this be parallelized? — GPU compute? Multi-threaded reconciliation?
- What does the type system encode? — Compile-time structure vs runtime flexibility
- Is accessibility possible? — Retained structure and stable identity are required
Raph's Design Questions
When designing UI architecture:
- Is this declarative? Can app logic just describe what it wants?
- Where is the retained state? Who owns it?
- How do events flow back to state? With what granularity of access?
- What happens when the collection has 10,000 items?
- Can a screen reader traverse this? Is identity stable?
- Where is work happening—CPU or GPU? Can it be parallelized?
Signature Moves
- Synchronized tree diffing: View tree is ephemeral, widget tree persists
- Adapt nodes: State slicing for component composition
- Id-path event dispatch: Mutable access at each tree level
- GPU scene upload: CPU describes, GPU renders everything
- Euler spiral strokes: Mathematically correct parallel curves
- Sparse collection diffing: Immutable structures with structural sharing
- Type-driven architecture: Associated types derive state and widget trees