Migration Guide
From Manual GPUI to RSX
Section titled “From Manual GPUI to RSX”GPUI-RSX can be adopted incrementally. Manual GPUI builders and RSX output are both normal GPUI elements, so they can live side by side.
Elements and Attributes
Section titled “Elements and Attributes”Before:
div() .flex() .flex_col() .gap(px(16.0)) .bg(rgb(0x3b82f6))After:
rsx! { <div class="flex flex-col gap-4 bg-blue-500" />}Use direct attributes when the source code already has a typed Rust value:
rsx! { <div w={px(width)} bg={self.background_color()} />}Children
Section titled “Children”Before:
div() .child("Hello") .child(div().child("World")) .child(button().child("Click"))After:
rsx! { <div> "Hello" <span>{"World"}</span> <button>{"Click"}</button> </div>}Events
Section titled “Events”Before:
button() .on_click(cx.listener(|view, _event, _window, cx| { view.count += 1; cx.notify(); })) .child("Click")After:
rsx! { <button onClick={cx.listener(|view, _event, _window, cx| { view.count += 1; cx.notify(); })}> {"Click"} </button>}Before:
let mut list = div().flex().flex_col();
for item in &self.items { list = list.child(div().child(item.name.clone()));}
listAfter:
rsx! { <div class="flex flex-col"> {for item in &self.items { <div>{item.name.as_str()}</div> }} </div>}If rows are stateful, add key={...}:
rsx! { <div class="flex flex-col"> {for item in &self.items { <button key={item.id} onClick={handler}> {item.name.as_str()} </button> }} </div>}Conditional Rendering
Section titled “Conditional Rendering”Use Rust conditionals for different children:
rsx! { <div> {if show_message { rsx! { <span>{"Message visible"}</span> } } else { rsx! { <span>{"Message hidden"}</span> } }} </div>}Use when when the same element receives optional builder calls:
rsx! { <div when={(show_message, |el| el.child("Message visible"))} />}0.5.x / crates.io GPUI 0.2.2 to 0.6.0 / Zed Git GPUI
Section titled “0.5.x / crates.io GPUI 0.2.2 to 0.6.0 / Zed Git GPUI”GPUI-RSX 0.6.x targets GPUI from the Zed repository, not the crates.io gpui = "0.2.2" package.
The Zed git dependency still reports the package version as gpui v0.2.2, but its API surface is different from the crates.io release. This matters for generated calls such as flex_grow_1, flex_shrink_1, alignment helpers, desktop sizing utilities, and the newer startup path.
Replace Dependencies
Section titled “Replace Dependencies”Before:
[dependencies]gpui = "0.2.2"gpui-rsx = "0.5"After:
[dependencies]gpui = { git = "https://github.com/zed-industries/zed" }gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "runtime_shaders", "wayland", "x11"] }gpui-rsx = "0.6"Applications should commit Cargo.lock to pin the exact resolved Zed revision.
If your app also depends on gpui-component, keep direct gpui and gpui_platform dependencies on the same source and revision resolved by gpui-component. Mixing a bare Zed git dependency with another pinned rev can create duplicate GPUI crate instances and incompatible component types.
Update Application Startup
Section titled “Update Application Startup”Before:
Application::new().run(|cx: &mut App| { cx.open_window(WindowOptions::default(), |_, cx| cx.new(|_| MyView));});After:
use gpui_platform::application;
application().run(|cx: &mut App| { cx.open_window(WindowOptions::default(), |_, cx| { cx.new(|_| MyView) }).unwrap(); cx.activate(true);});Verify the Dependency Graph
Section titled “Verify the Dependency Graph”Use the demo crate as a known-good integration target:
cargo check --manifest-path demo/Cargo.toml --bins --lockedcargo tree --manifest-path demo/Cargo.toml --locked -i gpuiIf cargo tree shows git+https://github.com/zed-industries/zed#..., the project is using Zed git GPUI even if the displayed package version is 0.2.2.
Incremental Migration Plan
Section titled “Incremental Migration Plan”- Add
gpui_rsx::rsximports in one view module. - Convert a small static subtree first.
- Convert child lists and repeated rows after the static structure compiles.
- Add
key={...}only to repeated elements that need stateful IDs. - Run
cargo checkafter each meaningful step.
Hybrid code is fine:
rsx! { <div> {self.legacy_gpui_builder()} <section class="flex flex-col gap-4"> {self.render_new_rsx_panel()} </section> </div>}Common Migration Patterns
Section titled “Common Migration Patterns”| Manual GPUI | GPUI-RSX |
|---|---|
div().child("Hello") | rsx! { <div>{"Hello"}</div> } |
div().child(format!("Count: {count}")) | rsx! { <div>{format!("Count: {count}")}</div> } |
.w(px(200.0)).h(px(100.0)) | <div w={px(200.0)} h={px(100.0)} /> |
.flex().gap(px(4.0)) | <div class="flex gap-4" /> |
| builder constructor with arguments | <Button base={Button::new("id")} /> |
Checklist
Section titled “Checklist”- Dependencies use Zed git GPUI for
gpuiandgpui_platform. - Application startup uses
gpui_platform::application(). -
Cargo.lockis committed for applications. -
gpui-component, if present, resolves to the same GPUI source. - New RSX modules import
gpui_rsx::rsx. - Repeated stateful elements have
key={...}or explicitid={...}. - The migrated view passes
cargo check. - Interactions still call
cx.notify()after state changes.