Skip to content

Syntax Reference

This page covers RSX syntax. For class utility details, see Class Handling. For generated identity rules, see IDs and Keys.

RSX elements use an HTML-like shape:

rsx! {
<tag attr value={expr}>
{child_expr}
</tag>
}

Common HTML-like tags map to div() because GPUI does not expose browser DOM nodes:

GroupTags
Structurediv, span, section, article, header, footer, main, nav, aside
Texth1, h2, h3, h4, h5, h6, p, label, a
Formsbutton, input, textarea, select, form
Listsul, ol, li

Self-closing and paired tags are equivalent when there are no children:

rsx! { <div flex /> }
rsx! { <div flex></div> }

Some tags use GPUI constructors directly:

RSXGenerated constructor
<svg src={path} />svg().path(path)
<img src={source} />img(source)
<img source={source} />img(source)
<canvas prepaint={prepaint} paint={paint} />canvas(prepaint, paint)

img requires src or source. canvas requires both prepaint and paint.

Unknown single-segment tags are treated as constructor functions:

rsx! {
<MyComponent title={"Inbox"} />
}

Path-qualified tags call the path:

rsx! {
<ui::TaskCard title={task.title.clone()} />
}

Use base={expr} when the builder needs arguments or comes from a component library:

rsx! {
<Button
base={Button::new("save")}
label={"Save"}
small
/>
}

base is consumed by the macro. It does not generate a .base(...) method call.

Flag attributes become zero-argument methods:

rsx! { <div flex flex_col items_center /> }

Value attributes become one-argument methods:

rsx! {
<div gap={px(16.0)} bg={rgb(0x3b82f6)} />
}

Multi-argument GPUI methods use tuple syntax:

rsx! {
<div onMouseDown={(MouseButton::Left, handler)} />
}

CamelCase aliases map to GPUI snake_case methods where needed:

RSX attributeGPUI method
onClickon_click
onKeyDownon_key_down
onMouseDownon_mouse_down
textColortext_color
backgroundColorbg
fontSizetext_size
minWidth / maxHeightmin_w / max_h
overflowScrolloverflow_scroll
trackScrolltrack_scroll

whiteSpace is intentionally rejected. Use GPUI’s supported whitespace helpers through class utilities such as whitespace-nowrap, or direct GPUI methods when available.

String literals are valid children:

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

Rust expressions are wrapped in braces:

rsx! {
<div>
{format!("Count: {}", self.count)}
{self.render_footer()}
</div>
}

Nested elements become .child(...) calls:

rsx! {
<section>
<h1 styled>{"Title"}</h1>
<p>{"Body"}</p>
</section>
}

Use spread children for existing iterables:

rsx! {
<div>
{...rows}
</div>
}

Use {for pattern in iter { ... }} to render an iterator as children:

rsx! {
<ul>
{for (index, item) in items.iter().enumerate() {
<li>{format!("{}: {}", index, item.name)}</li>
}}
</ul>
}

If a loop body contains stateful elements, add key={...} or id={...}. See IDs and Keys.

Fragments return multiple root elements:

rsx! {
<>
<div>{"First"}</div>
<div>{"Second"}</div>
</>
}

A fragment expands to vec![...]. Mixed concrete root types may need into_any_element() or a common wrapper element.

Rust expressions work inside children:

rsx! {
<div>
{if let Some(user) = &self.user {
rsx! { <span>{user.name.as_str()}</span> }
} else {
rsx! { <span>{"Guest"}</span> }
}}
</div>
}

Use when to conditionally transform the element being built:

rsx! {
<div
class="px-4 py-2"
when={(active, |el| el.bg(rgb(0x3b82f6)).text_color(rgb(0xffffff)))}
>
{"Status"}
</div>
}

Use whenSome when the conditional value is an Option:

rsx! {
<div whenSome={(custom_width, |el, width| el.w(px(width)))} />
}

Use whenClass for conditional static class strings:

rsx! {
<div
class="px-2"
whenClass={(active, "bg-neutral-900 text-white")}
whenClass={(!active, "text-neutral-600")}
/>
}

whenClass requires a string literal and rejects stateful classes such as overflow-scroll. Use when for ID-sensitive methods.

visible={bool} maps to .visible() or .invisible() while evaluating the expression once:

rsx! {
<div visible={self.show_sidebar} />
}

grayscale maps to .grayscale(true):

rsx! {
<img src={source} grayscale />
}

styled injects default classes for semantic tags before user attributes:

TagDefault classes
h1text-3xl font-bold
h2text-2xl font-bold
h3text-xl font-bold
h4text-lg font-bold
h5text-base font-bold
h6text-sm font-bold
button, acursor-pointer
input, textareapx-2 py-1
ul, olflex flex-col
liflex items-center
ptext-base
labeltext-sm
formflex flex-col gap-4

User attributes are applied after styled, so later values can override defaults:

rsx! {
<h1 styled class="text-xl">{title.as_str()}</h1>
}