gpui-markup
A declarative markup DSL for building GPUI applications.
Installation
Usage
use *;
use ui;
Syntax
Elements
All elements require braces {}. Attributes go before braces with @[...]:
// Empty div
ui!
// -> div()
// Div with attributes
ui!
// -> div().flex().flex_col()
// Div with children
ui!
// -> gpui::ParentElement::child(div(), "content")
// Full form: attributes before braces, children inside
ui!
// -> gpui::ParentElement::child(div().flex(), "content")
Attributes
Attributes use @[...] before braces, comma-separated:
// Flag attributes (no value)
ui!
// -> div().flex().flex_col()
// Key-value attributes
ui!
// -> div().w(px(200.0)).h(px(100.0))
// Multi-value attributes (use tuples)
ui!
// -> div().when(condition, |d| d.bg(red()))
Children
Children go inside {...}, comma-separated:
ui!
// -> gpui::ParentElement::child(
// gpui::ParentElement::child(
// gpui::ParentElement::child(div(), "First"),
// "Second"
// ),
// gpui::ParentElement::child(div().bold(), "Nested")
// )
Deferred
The deferred element wraps content for deferred rendering:
ui!
// -> deferred(gpui::IntoElement::into_any_element(gpui::ParentElement::child(div(), "Deferred content")))
Spread Children
Use ..expr to spread an iterable as children:
let items: = vec!;
ui!
// -> gpui::ParentElement::children(div(), items)
// Can be mixed with regular children
ui!
// -> gpui::ParentElement::child(
// gpui::ParentElement::children(
// gpui::ParentElement::child(div(), "Header"),
// items
// ),
// "Footer"
// )
Method Chains
Use .method(args) to insert method calls at any position:
ui!
Comments
Use standard Rust comments inside ui!:
ui!
// -> gpui::ParentElement::child(div(), "Visible content")
Components
Components are any uppercase non-native element names. They automatically call ::new():
// Simple component
ui!
// -> Header::new()
// Component with attributes
ui!
// -> Button::new().style(Primary)
// Component with children
ui!
// -> gpui::ParentElement::child(gpui::ParentElement::child(Container::new(), "Content"), Footer::new())
Expression Elements
Any expression can be used as an element at the top level (braces required):
// Custom constructor
ui!
// -> Button::with_label("Click")
// Expression with attributes
ui!
// -> Button::with_label("Click").style(Primary)
// Builder pattern expression
ui!
// -> gpui::ParentElement::child(div().flex().flex_col(), "Content")
// Parentheses for complex expressions (braces optional)
ui!
// -> a + b
Why braces are required at top level?
The ui! macro builds a GPUI component tree. At the top level, {} declares "this is a UI element":
- Marks this as a node in the component tree, not just an expression
- For components,
{}triggers the implicit::new()call - Provides a place for attributes
@[...]and children
// Clear: defining a UI element, Header::new() is called
ui!
// As a child, context already indicates it's part of the tree
div
Closure Parameters as Elements
When using closure parameters (e.g., from .when(), .map()) in nested ui! macros, lowercase identifiers are treated as expression elements, not components:
// Closure parameter as element
ui!
// -> div().when(selected, |s| { s })
// With attributes and children
ui!
// -> div().when(condition, |styled| {
// gpui::ParentElement::child(styled.flex().gap_2(), "Content")
// })
How it works:
- Uppercase identifiers (e.g.,
Header,Button) → Components, call::new()implicitly - Lowercase native elements (
div,svg,anchored) → Native GPUI elements - Other lowercase identifiers (e.g.,
s,element,styled) → Expression elements (variables, parameters)
This allows seamless use of closure parameters from GPUI's builder methods like .when(), .map(), .hover(), etc.
Nested Structures
ui!
How It Works
The ui! macro transforms the markup syntax into GPUI's builder pattern at compile time:
| Markup | Generated Code |
|---|---|
div {} |
div() |
div @[flex] {} |
div().flex() |
div @[w: x] {} |
div().w(x) |
div @[when: (a, b)] {} |
div().when(a, b) |
div { a, b } |
gpui::ParentElement::child(gpui::ParentElement::child(div(), a), b) |
div { ..items } |
gpui::ParentElement::children(div(), items) |
div { .a().b() } |
div().a().b() |
deferred { e } |
deferred(gpui::IntoElement::into_any_element(e)) |
Header {} |
Header::new() |
Header @[a] {} |
Header::new().a() |
expr {} |
expr |
expr @[a] {} |
expr.a() |
(expr) |
expr |