GPUI-RSX
English | 简体中文
A Rust procedural macro that provides JSX-like syntax for GPUI, making UI development more concise and intuitive.
✨ Features
- 🎨 HTML-like Syntax - React JSX-like development experience
- 🚀 Zero Runtime Overhead - Expands to native GPUI code at compile time
- 📦 Lightweight - Only depends on
syn,quote,proc-macro2 - 🔧 Flexible - Supports expressions, conditional rendering, component composition
- 💡 Type Safe - Full compile-time type checking
- 🧩 Fragment Support - Return multiple root elements with
<>...</> - 🔁 For-loop Sugar - Iterate with
{for item in iter { ... }} - 🔑 Loop-safe IDs -
key={expr}generates unique IDs per iteration; compile error on missing key - 🎨 Full Tailwind Colors - 242 built-in colors + arbitrary hex/RGB/RGBA values
- 📐 Desktop Layout Utilities - Arbitrary lengths, percentages, and fraction sizing for panels and split views
- ⚡ Dynamic Class - Runtime class switching with colors, sizing, spacing, and numeric prefix fallback
- 🔍 Diagnostics & Preview - Strict/permissive macros, readable errors, and
rsx_expand!
📚 Documentation
- Architecture Guide - Detailed architecture documentation
- Module organization and data flow
- Code generation strategies
- Design patterns and testing approach
- Extension points and debugging guide
- Getting Started - Step-by-step tutorial
- API Reference - Complete API documentation
- Best Practices - Recommended patterns
- Migration Guide - Upgrade instructions
- Troubleshooting - Common issues and solutions
📦 Installation
Add to your Cargo.toml:
[]
= "0.2"
= "0.4"
🚀 Quick Start
Get Started in 5 Minutes
use *;
use rsx;
Before & After
❌ Traditional GPUI (Verbose)
✅ With GPUI-RSX (Concise)
See the Quick Start example above.
Code Reduction: ~50% ✨
📖 Syntax Guide
1. Basic Elements
rsx!
Expands to:
div.child
2. Fragment (Multiple Root Elements)
When you need to return multiple elements without a wrapper:
rsx!
Expands to:
vec!
3. Attributes
Boolean Attributes (Flags)
rsx!
Expands to:
div.flex.flex_col
Value Attributes
rsx!
Expands to:
div.gap.bg
4. Class Attribute
The class attribute accepts a Tailwind-like string that expands into multiple GPUI method calls:
rsx!
Expands to:
div.flex.flex_col.gap.p
Note:
classaccepts both static strings (compiled at build time) and dynamic expressions (parsed at runtime). GPUI-RSX implements a Tailwind-like subset, not the full Tailwind CSS engine. Static classes expand directly to GPUI builder calls; dynamic class expressions use a small runtime matcher. See Dynamic Class for details.
Supported class patterns
Layout:
flex,flex-col,flex-row,flex-wrap,flex-1,flex-none,flex-automin-w-0,min-h-0,items-center,items-start,items-end,items-stretchjustify-center,justify-between,justify-around,justify-evenly
Spacing (numeric values become px(n)):
gap-4→.gap(px(4.0))p-4,px-4,py-4,pt-4,pb-4,pl-4,pr-4m-4,mx-4,my-4,mt-4,mb-4,ml-4,mr-4- Arbitrary spacing:
gap-[14px],gap-x-[0.75rem],p-[18px],mx-[1.25rem] - Percent spacing such as
gap-[10%]intentionally errors because GPUI spacing uses definite lengths
Sizing:
- Numeric values keep project semantics:
w-64→.w(px(64.0)),h-32→.h(px(32.0)) w-full,h-full,size-full,aspect-squarew-px,h-px,w-auto,h-auto,w-1/2,h-1/3,size-1/2- Arbitrary sizing:
w-[280px],w-[18rem],w-[37.5%],min-w-[280px],max-w-[32rem] - Fraction sizing with arbitrary denominators:
w-6/24,min-w-1/3,size-3/4
Text:
text-xs,text-sm,text-base,text-lg,text-xltext-2xl,text-3xlfont-thin,font-extralight,font-light,font-normal,font-medium,font-semibold,font-bold,font-extrabold,font-blackwhitespace-normal,whitespace-nowrap,line-clamp-*text-ellipsis,text-ellipsis-start,truncate,no-underlinetext-decoration-solid,text-decoration-wavy,text-decoration-0/1/2/4/8
Alignment:
content-normal,content-center,content-start,content-end,content-between,content-around,content-evenly,content-stretchself-start,self-end,self-flex-start,self-flex-end,self-center,self-baseline,self-stretch
Border:
border→.border_1()border-2→.border_2(),border-4→.border_4()rounded-sm,rounded-md,rounded-lg,rounded-xl,rounded-full,rounded-none
Colors (full Tailwind palette):
text-red-500→.text_color(rgb(0xef4444))bg-blue-600→.bg(rgb(0x2563eb))border-green-500→.border_color(rgb(0x22c55e))- Arbitrary colors:
bg-[#ff0000],text-[#333],border-[#11223344],bg-[rgb(15,23,42)],text-[rgba(15,23,42,0.8)]
Effects:
shadow-none,shadow-2xs,shadow-xs,shadow-sm,shadow-md,shadow-lg,shadow-xl,shadow-2xloverflow-hidden,overflow-x-hidden,overflow-y-hidden,overflow-scrollcursor-pointer,cursor-default,cursor-text,cursor-move,cursor-grab,cursor-not-allowed, resize cursor variantsdebug-outlineenables GPUI debug borders in debug builds and is a no-op in release builds
Grid placement:
col-span-*,col-start-*,col-end-*,row-span-*,row-start-*,row-end-*col-span-full,col-start-auto,col-end-auto,row-span-full,row-start-auto,row-end-auto
Supported colors: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose (shades 50-950) + white, black
5. Event Handling
rsx!
Supported events (camelCase / snake_case):
| Event | Method |
|---|---|
onClick / on_click |
.on_click(handler) |
onMouseDown / on_mouse_down |
.on_mouse_down(button, handler) |
onMouseUp / on_mouse_up |
.on_mouse_up(button, handler) |
onMouseMove / on_mouse_move |
.on_mouse_move(handler) |
onMouseDownOut / on_mouse_down_out |
.on_mouse_down_out(handler) |
onMouseUpOut / on_mouse_up_out |
.on_mouse_up_out(button, handler) |
onAnyMouseDown / on_any_mouse_down |
.on_any_mouse_down(handler) |
onAnyMouseUp / on_any_mouse_up |
.on_any_mouse_up(handler) |
onKeyDown / on_key_down |
.on_key_down(handler) |
onKeyUp / on_key_up |
.on_key_up(handler) |
onModifiersChanged / on_modifiers_changed |
.on_modifiers_changed(handler) |
onHover / on_hover |
.on_hover(handler) |
onScrollWheel / on_scroll_wheel |
.on_scroll_wheel(handler) |
onDrag / on_drag |
.on_drag(value, constructor) |
onDragMove / on_drag_move |
.on_drag_move(handler) |
onDrop / on_drop |
.on_drop(handler) |
onAction / on_action |
.on_action(handler) |
onBoxedAction / on_boxed_action |
.on_boxed_action(action, handler) |
captureAnyMouseDown / capture_any_mouse_down |
.capture_any_mouse_down(handler) |
captureAnyMouseUp / capture_any_mouse_up |
.capture_any_mouse_up(handler) |
captureKeyDown / capture_key_down |
.capture_key_down(handler) |
captureKeyUp / capture_key_up |
.capture_key_up(handler) |
captureAction / capture_action |
.capture_action(handler) |
Methods with multiple GPUI parameters use tuple syntax in RSX:
onMouseDown={(MouseButton::Left, handler)} and onDrag={(value, constructor)}.
6. Nested Elements
rsx!
7. Expressions
rsx!
8. List Rendering
Using iterators (traditional)
rsx!
Using for-loop syntax sugar
rsx!
Expands to:
div.children
Loop safety — key attribute
Elements with stateful attributes (onClick, onHover, onDrag, tooltip,
focusable, overflowScroll, trackScroll, or overflow-scroll) inside a for-loop must
provide id or key, otherwise the macro emits a compile error:
// ❌ compile error — all <li> would share the same auto ID
// ✅ key makes every ID unique per iteration
// → div().id(format!("src/list.rs::__rsx_li_L42C8_{}", item.id)).on_click(handler)…
key is consumed by the macro and does not become a .key() method call.
It accepts any type implementing Display. On elements without stateful attributes,
key is silently ignored (no .id() is injected).
For-loops also support ranges and method calls:
rsx!
9. Spread Syntax
rsx!
10. Dynamic Class
GPUI-RSX supports both static and dynamic class attributes.
Static Class (Compile-time — Recommended)
// ✅ Best performance - parsed at compile time, supports the documented subset
rsx!
Dynamic Class (Runtime)
let classes = if is_active else ;
rsx!
Supported at runtime: common layout/spacing/typography utilities, the full Tailwind color palette, arbitrary colors (e.g.
bg-[#ff0000],text-[#f00],bg-[rgba(15,23,42,0.8)]), arbitrary spacing and sizing lengths (w-[280px],h-[50%],gap-[14px],mx-[1.25rem]), fraction sizing (w-6/24), and numeric prefix fallback (e.g.gap-7,p-5,opacity-33). Truly unsupported classes (e.g. Tailwind variants or unknown utilities) are silently ignored in release builds and print a warning in debug builds.Recommended alternatives (in priority order):
- String literal (best):
class="flex gap-4"— compile-time, supports the documented subset- Conditional literal:
class={if active { "flex gap-4" } else { "block" }}— still a literal- Individual attributes:
<div flex gap_4 />— compile-time, type-checkedwhenattribute:when={(cond, |el| el.flex())}— compile-time, fully flexible- Dynamic expression:
class={expr}— runtime parser, narrower coverage than static literals
Common Patterns:
// ✅ Conditional literal (compile-time, documented subset)
let button_class = if primary else ;
// ✅ when attribute (compile-time, fully flexible)
rsx!
// ✅ Dynamic string with numeric prefix and arbitrary hex colors
let classes = format!; // gap-7, gap-32, etc. all work
11. Macro Modes and Expansion Preview
rsx! is permissive by default: unsupported static class names are ignored when they cannot be parsed safely, while invalid arbitrary values emit compile errors. Use rsx_strict! to reject unsupported static classes:
use ;
rsx_strict!
rsx_permissive!
let preview = rsx_expand! ;
assert!;
Strict dynamic classes panic when an unsupported runtime token is evaluated. rsx_expand! returns a string preview for debugging and does not type-check the generated GPUI expression.
12. Desktop Three-Column Layout
rsx!
min-w-0 is important in desktop split layouts because it allows the center pane to shrink instead of pushing fixed sidebars out of the window.
13. Attribute Mapping Reference
camelCase attributes are automatically mapped to GPUI snake_case methods:
| RSX Attribute | GPUI Method |
|---|---|
opacity |
.opacity() |
visible / invisible |
.visible() / .invisible() |
width / height |
.w() / .h() |
minWidth / maxWidth |
.min_w() / .max_w() |
minHeight / maxHeight |
.min_h() / .max_h() |
gapX / gapY |
.gap_x() / .gap_y() |
flexBasis |
.flex_basis() |
flexGrow / flexShrink (flags) |
.flex_grow() / .flex_shrink() |
fontSize |
.text_size() |
lineHeight |
.line_height() |
fontWeight |
.font_weight() |
textAlign |
.text_align() |
borderTop / borderBottom |
.border_t(value) / .border_b(value) |
borderLeft / borderRight |
.border_l(value) / .border_r(value) |
border_t / border_b / border_l / border_r (flags) |
.border_t_1() / .border_b_1() / .border_l_1() / .border_r_1() |
roundedTop / roundedBottom |
.rounded_t() / .rounded_b() |
roundedTopLeft / roundedTopRight |
.rounded_tl() / .rounded_tr() |
roundedBottomLeft / roundedBottomRight |
.rounded_bl() / .rounded_br() |
boxShadow |
.shadow() |
inset |
.inset() |
Attributes not in this table are passed through as-is (e.g., bg={color} → .bg(color)).
14. Conditional Styling with when and whenSome
when - Apply styles based on condition
rsx!
whenSome - Apply styles when Option has value
let custom_width: = Some;
rsx!
Multiple conditions
rsx!
15. Styled Flag (Default Tag Styles)
The styled flag injects sensible default styles based on the tag name:
rsx!
Default styles per tag:
| Tag | Default Styles |
|---|---|
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 defaults and can override them.
🎯 Complete Example
Todo App
use *;
use rsx;
🔧 Advanced Usage
Custom Components
Conditional Rendering
rsx!
Dynamic Styling
📊 Performance
GPUI-RSX is a compile-time macro that expands static RSX into direct GPUI builder calls. Static markup has no parser at runtime; dynamic class={expr} intentionally uses a small runtime matcher.
| Metric | Traditional GPUI | GPUI-RSX |
|---|---|---|
| Code Size | 100 lines | 50 lines (-50%) |
| Runtime Performance | Baseline | Same |
| Type Safety | ✅ | ✅ |
| Compile-time Checking | ✅ | ✅ |
v0.3.2 Fixes & Improvements
- Fixed
parse_single_classpanic on Tailwind variant syntax (hover:bg-blue-500): invalid class names are now silently skipped instead of callingsyn::Ident::newwith illegal characters - Added 7 classes to dynamic match table:
rounded-none,rounded-xl,cursor-default,cursor-text,shadow-sm,shadow-md,shadow-lg - Docs styled defaults: added
li,p,label,formentries; fixedoverflowX/overflowYmethod names; removed unsupportedtext-4xl/text-5xl; updated dynamic class description
v0.3.1 Fixes & Features
- Fixed
is_stateful_attr:hover/active/focus/groupareStyledtrait methods and no longer trigger unnecessary.id()injection - Added
key={expr}attribute: composite auto ID for stateful elements in for-loops - Added compile error when a stateful element in a for-loop has no
idorkey keyon non-stateful elements is silently ignored (no unintended type change toStateful<Div>)
v0.3.0 Refactoring
- Eliminated ~60 duplicate method definitions in
tests/common/mod.rs(823 → 456 lines) - Simplified black/white color entry generation in
runtime.rs(method names encoded in data) - Extracted
is_directional_border()helper inclass.rsfor clearer border logic
v0.2.2 Optimizations
Compile-time Performance:
split_ascii_whitespacereplacessplit_whitespacein class parsing- Unified
text_prefix handling (singlestrip_prefixcall) - Early fast-path for empty elements
Vec::with_capacity(attrs * 2 + children)for class-heavy elements
Runtime Performance:
.children([...])batching threshold lowered 3 → 2
Binary Size:
- Applications may opt into
panic = "abort"in their own release profile to remove unwind tables
v0.2.1 Optimizations
Compile-time Performance:
- O(1) color / attribute / spacing lookups via
match(jump table, no linear scan) - Single-pass attribute scanning in
generate_element - Thread-local cache for dynamic class match arms (generated once per process)
Memory Allocation Reductions:
parse_class_stringreturns an iterator (no intermediateVec)generate_attr_methodspushes directly into caller's bufferCow<str>for class name transformations (zero-copy when no-present)Vec::with_capacitypre-allocation throughout
Runtime Performance:
- Zero-copy dynamic class strings via
AsRef<str>(&strneeds no allocation)
Binary Size:
- Dynamic class match table extracted to
#[inline(never)]+ LLVM ICF deduplication - Multiple
class={expr}in same component share one function body
🛠️ Development
Build
Test
Expand Macros (Debugging)
# Install cargo-expand
# View expanded code
💡 Best Practices
1. Component Splitting
Break complex UIs into small, reusable components:
// ✅ Recommended: Split into multiple methods
2. Use Constants
Extract repeated styles as constants:
const PRIMARY_BG: Rgb = rgb;
const PRIMARY_TEXT: Rgb = rgb;
rsx!
3. Avoid Over-nesting
// ❌ Not recommended: Over-nested
rsx!
// ✅ Recommended: Flatten structure
rsx!
🐛 FAQ
Q1: How to use variables in RSX?
let title = "Hello";
rsx!
Q2: How to handle Option types?
rsx!
Q3: What does the expanded macro code look like?
Use rsx_expand! for a local string preview, or cargo expand to inspect the full crate:
let preview = rsx_expand! ;
Q4: Which elements are supported?
All GPUI-supported elements can be used, such as div, button, input, span, etc.
Q5: Can I use dynamic class values?
Yes, but with an important limitation:
// ✅ Static literal (compile-time, documented subset — recommended)
rsx!
// ✅ Individual attributes (compile-time, type-checked)
rsx!
// ✅ Conditional styling with `when` (compile-time, fully flexible)
rsx!
// ✅ Dynamic expression with numeric prefix, arbitrary lengths, and arbitrary colors
let classes = if active else ;
rsx!
// Tailwind variants and unknown utilities are ignored in permissive mode.
Tip: When you need dynamic styling, prefer when/whenSome or individual value
attributes (bg={color}) — they are compile-time and support everything GPUI offers.
🤝 Contributing
Contributions are welcome! Feel free to submit Issues or Pull Requests.
Development Workflow
- Fork the project
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push branch:
git push origin feature/amazing-feature - Submit a Pull Request
Code Standards
- Use
rustfmtto format code - Use
clippyto check code quality - Add tests for new features
- Update documentation
📝 License
MIT License
🙏 Acknowledgments
Inspired by:
- Dioxus RSX - RSX syntax design
- Yew html! macro - html! macro
- React JSX - JSX syntax
- GPUI - Underlying UI framework
Make GPUI development more enjoyable! 🎉