Syntax Reference
This page covers RSX syntax. For class utility details, see Class Handling. For generated identity rules, see IDs and Keys.
Elements
Section titled “Elements”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:
| Group | Tags |
|---|---|
| Structure | div, span, section, article, header, footer, main, nav, aside |
| Text | h1, h2, h3, h4, h5, h6, p, label, a |
| Forms | button, input, textarea, select, form |
| Lists | ul, ol, li |
Self-closing and paired tags are equivalent when there are no children:
rsx! { <div flex /> }rsx! { <div flex></div> }Special Tags
Section titled “Special Tags”Some tags use GPUI constructors directly:
| RSX | Generated 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.
Components
Section titled “Components”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.
Attributes
Section titled “Attributes”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 attribute | GPUI method |
|---|---|
onClick | on_click |
onKeyDown | on_key_down |
onMouseDown | on_mouse_down |
textColor | text_color |
backgroundColor | bg |
fontSize | text_size |
minWidth / maxHeight | min_w / max_h |
overflowScroll | overflow_scroll |
trackScroll | track_scroll |
whiteSpace is intentionally rejected. Use GPUI’s supported whitespace helpers through class utilities such as whitespace-nowrap, or direct GPUI methods when available.
Children
Section titled “Children”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>}For Loops
Section titled “For Loops”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
Section titled “Fragments”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.
Conditional Rendering
Section titled “Conditional Rendering”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.
Special Attributes
Section titled “Special Attributes”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:
| Tag | Default classes |
|---|---|
h1 | text-3xl font-bold |
h2 | text-2xl font-bold |
h3 | text-xl font-bold |
h4 | text-lg font-bold |
h5 | text-base font-bold |
h6 | text-sm font-bold |
button, a | cursor-pointer |
input, textarea | px-2 py-1 |
ul, ol | flex flex-col |
li | flex items-center |
p | text-base |
label | text-sm |
form | flex 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>}