Skip to content

Troubleshooting

Example error:

Closing tag `</span>` does not match opening tag `<div>`.

Fix the nesting:

// Wrong
rsx! {
<div>
<span>{"Text"}</div>
</span>
}
// Correct
rsx! {
<div>
<span>{"Text"}</span>
</div>
}

Add the missing closing tag:

// Wrong
rsx! {
<div>{"Content"}
}
// Correct
rsx! {
<div>{"Content"}</div>
}

Bare identifiers are not children. Wrap expressions in braces:

// Wrong
rsx! { <div>count</div> }
// Correct
rsx! { <div>{count}</div> }

String literals can be written directly:

rsx! { <div>"Count"</div> }

The loop body must be a braced RSX body:

// Wrong
rsx! {
<ul>
{for item in items
<li>{item}</li>
}
</ul>
}
// Correct
rsx! {
<ul>
{for item in items {
<li>{item}</li>
}}
</ul>
}

rsx_strict! rejects unsupported static classes:

rsx_strict! {
<div class="hover:bg-blue-500" />
}

Use a supported class, a direct GPUI method, when, or rsx_permissive! if ignoring unsupported classes is intentional.

Arbitrary length classes must use supported units and finite values:

// Correct
rsx! { <div class="w-[280px] max-w-[37.5%] gap-[14px]" /> }

Sizing supports px, rem, percentages, and fractions. Spacing supports definite lengths such as px and rem; percentage spacing is rejected.

Use supported hex, RGB, or RGBA forms:

rsx! {
<div class="bg-[#ff0000] text-[rgb(15,23,42)] border-[rgba(15,23,42,0.35)]" />
}

RGBA alpha must be in the 0.0..=1.0 range.

Dynamic classes use a runtime matcher. Unsupported tokens are ignored in permissive mode and warn once per call site in debug builds.

If a dynamic class must always be applied, prefer one of these:

rsx! {
<div
class="flex"
w={px(width)}
whenClass={(active, "bg-blue-500 text-white")}
/>
}

Use rsx_strict! to panic when an unsupported dynamic token is evaluated.

These attributes expect exactly two tuple values:

rsx! {
<div when={(active, |el| el.bg(rgb(0x3b82f6)))} />
<div whenSome={(width, |el, w| el.w(px(w)))} />
<div whenClass={(active, "bg-blue-500 text-white")} />
}

This is invalid:

let classes = "bg-blue-500";
rsx! {
<div whenClass={(active, classes)} />
}

Use when for dynamic styling, or put the literal directly in whenClass.

overflow-scroll, overflow-x-scroll, and overflow-y-scroll need ID semantics. Use when with explicit GPUI calls:

rsx! {
<div when={(scrollable, |el| el.overflow_scroll())} />
}

Repeated stateful elements need unique IDs:

// Wrong
rsx! {
<ul>
{for item in &self.items {
<li onClick={handler}>{item.name.as_str()}</li>
}}
</ul>
}
// Correct
rsx! {
<ul>
{for item in &self.items {
<li key={item.id} onClick={handler}>{item.name.as_str()}</li>
}}
</ul>
}

key only matters when the element needs an ID. On plain layout rows, it is ignored.

Auto IDs include source file, line, and column. If identity must survive moving code, use an explicit id:

rsx! {
<button id="settings-save" onClick={handler}>
{"Save"}
</button>
}

Children must be valid GPUI child values. Handle Option values explicitly:

rsx! {
<div>{self.optional_text.as_deref().unwrap_or("")}</div>
}

Or render conditionally:

rsx! {
<div>
{if let Some(text) = &self.optional_text {
rsx! { <span>{text.as_str()}</span> }
} else {
rsx! { <span /> }
}}
</div>
}

Iterate by reference:

rsx! {
<ul>
{for item in &self.items {
<li>{item.name.as_str()}</li>
}}
</ul>
}

Fragments expand to vec![...]. If root element concrete types differ, wrap them or erase the type:

rsx! {
<div>
{self.render_header()}
{self.render_custom_button()}
</div>
}

After mutating view state, call cx.notify() once:

onClick={cx.listener(|view, _, _window, cx| {
view.count += 1;
cx.notify();
})}

Also check that the rsx! expression is returned from render without a trailing semicolon:

fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
rsx! { <div>{"Content"}</div> }
}

Check the handler signature expected by the GPUI method you are calling. For repeated stateful event targets, ensure the element has a key or explicit id.

Use rsx_expand! to inspect generated methods:

let preview = gpui_rsx::rsx_expand! {
<div class="flex gap-4 bg-blue-500" />
};

Remember that GPUI-RSX is not Tailwind CSS. Unsupported variants such as hover:bg-blue-500 do not become hover styles.

If gpui-component types do not match your application types, inspect the dependency tree:

Terminal window
cargo tree --manifest-path demo/Cargo.toml --locked -i gpui

Keep all direct gpui and gpui_platform dependencies on the same Zed source and revision as the component library.

Demo Check Fails After Updating Dependencies

Section titled “Demo Check Fails After Updating Dependencies”

Run:

Terminal window
cargo check --manifest-path demo/Cargo.toml --bins --locked

If --locked fails because the lockfile needs changes, decide whether the GPUI target revision should intentionally move. Do not update the lockfile casually for application compatibility checks.

When filing an issue, include:

  • GPUI-RSX version.
  • The resolved GPUI source and revision from cargo tree -i gpui.
  • Minimal RSX snippet.
  • Full compiler error or runtime symptom.
  • Whether the issue reproduces in the demo/ crate.