You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add an optional HTML-like syntax layer for constructing GPUI element trees. The
syntax lowers directly to GPUI's existing fluent builder API and does not add a
virtual DOM, reconciliation, CSS runtime, or a second rendering model.
The feature lives outside the gpui and gpui_macros crates:
gpui_html is the public facade and contains small runtime helpers for
structural element identity.
gpui_html_macros contains the html! procedural macro parser and code
generator.
Applications opt in by depending on gpui_html. GPUI itself does not depend on
either crate.
Motivation
GPUI's fluent element API is explicit and type-safe, but deeply nested UIs can
be difficult to scan:
gpui does not re-export the macro and does not depend on gpui_html.
gpui_html_macros
gpui_html_macros is a proc-macro crate. It depends on syn, quote, and proc-macro2, but not on gpui.
The macro resolves the facade's actual Cargo dependency name and generates all
runtime paths through gpui_html, including a hidden re-export of gpui. This
supports renamed dependencies while keeping the parser independent from GPUI
runtime compilation and lets the syntax crate evolve or release separately.
Future Hooks
A future Hooks experiment should use another optional facade/runtime crate, for
example gpui_hooks, with a matching proc-macro crate only if function-component
syntax requires one. It should not be added to gpui_html merely because both
features use a React/Yew-inspired surface syntax.
Separating them lets GPUI evaluate three decisions independently:
whether an element DSL is useful;
whether element-local state APIs should be expanded;
whether function components need generated runtime boundaries.
Proposed syntax
Intrinsic elements
The initial intrinsic registry is intentionally small:
Attributes retain source order because GPUI's fluent API uses concrete and
stateful return types. Methods available only on the concrete element must appear
before id; methods requiring StatefulInteractiveElement, such as on_click,
must appear after id. Reordering arbitrary methods in the macro would make
other valid chains, such as svg().path(...).id(...), fail to compile.
The macro does not maintain a complete method or event registry. Rust method
resolution and trait bounds remain the source of truth.
A bare string literal lowers to gpui::text!(literal) so it receives a
source-derived accessibility ID. A braced expression may produce any IntoElement value.
The macro generates imperative ParentElement::extend calls. It does not create
an OptionElement, fragment element, or intermediate virtual tree.
The initial syntax requires an else branch for if and if let. This keeps
the parser and generated control flow explicit. Supporting an omitted branch can
be considered later.
Existing components and elements
The macro does not define a component props protocol. Existing GPUI values use a
dynamic tag:
The opening dynamic tag is <@{expression}>, and a non-empty dynamic tag closes
with </@>.
A value that does not need additional attributes can also be inserted as an
expression child:
html!{
<div>{Button::new("save")}</div>
}
Expansion model
The macro produces ordinary Rust expressions. A parent with conditional children
is conceptually expanded as follows:
{use::gpui::prelude::*;letmut element = ::gpui::div().flex();if show_details {::gpui::ParentElement::extend(&mut element,::core::iter::once(::gpui::IntoElement::into_any_element(::gpui::text!(details)),),);}else{::gpui::ParentElement::extend(&mut element,::core::iter::once(::gpui::IntoElement::into_any_element(::gpui::text!("No details")),),);}
element
}
The root remains its concrete element type. Type erasure occurs only at the
existing ParentElement child boundary.
Structural identity
GPUI's interactive .id(...) participates in hit testing and accessibility. A
React-style key has a different purpose: it identifies a structural subtree.
The proposal therefore adds a wrapper owned by gpui_html:
keyed(key, element)
scoped(element)
NamespaceElement returns the structural key from Element::id() and delegates
layout, prepaint, and paint to its inner AnyElement. It does not create a role,
hitbox, focus target, or event listener.
key={value} expands to gpui_html::keyed(value, element). An unkeyed dynamic
tag expands to gpui_html::scoped(element), which uses the tag's source location
as a default namespace.
Source-derived scope distinguishes component instances written at different
locations. Repeated instances created at the same call site still require an
explicit stable key.
This is structural identity, not reconciliation. GPUI continues to rebuild the
element tree using its existing rendering model.
Diagnostics
The macro should diagnose errors it can identify without duplicating GPUI's type
system:
empty input or multiple roots;
unsupported fragments;
unknown intrinsic tags, with a suggestion to use <@{expr}>;
mismatched or missing closing tags;
children on leaf tags;
missing or duplicate img.source;
invalid text children;
duplicate id or key;
unsupported class or CSS style attributes;
malformed attribute argument syntax.
The generated tokens retain source spans for tags, attributes, and child
expressions. Rust reports unavailable fluent methods, invalid argument types,
event closure signature errors, and missing IntoElement implementations.
Compatibility and adoption
The proposal requires no changes to existing GPUI applications. An application
opts in with:
[dependencies]
gpui = "..."gpui_html = "..."
Fluent builders and html! can coexist in the same render function. The macro
expands to public GPUI APIs, so applications can inspect expansions with normal
Rust tooling.
The standalone crate should initially version independently from GPUI. If the
experiment proves stable, the crates may follow GPUI's release version without
moving their implementation into gpui or gpui_macros.
Alternatives considered
Add html! to gpui_macros
This reduces the number of packages but couples a comparatively large DSL parser
to GPUI's core derive macros and release cadence. It also forces all GPUI users
to compile code they may not use. Separate crates provide a clearer experimental
boundary.
Name the macro ui! or view!
Those names avoid suggesting browser compatibility. html! is more immediately
recognizable to Yew users and code-generating tools. The crate documentation must
state that the syntax targets GPUI methods rather than HTML semantics. The final
name remains open for upstream discussion.
Add a virtual DOM
A virtual DOM could provide reconciliation and component lifecycle semantics,
but it would introduce a second tree and invalidate many assumptions in GPUI's
current element pipeline. It is not required to reduce builder nesting.
Generate fluent builder code with a declarative macro
A macro_rules! implementation avoids a proc-macro crate but cannot provide the
same nested grammar, closing-tag validation, or source-focused diagnostics.
Risks
Rust formatter and editor support inside custom tag syntax is less mature than
ordinary Rust method chains.
Macro diagnostics can regress if the grammar grows too broad.
Source-derived scopes collide when a component is repeated at one call site
without key.
Preserving attribute order exposes GPUI's builder type-state constraints in the
DSL; diagnostics for a misplaced id may come from Rust method resolution.
The name html! may cause users to expect CSS and DOM behavior.
A large intrinsic/property registry would duplicate GPUI APIs and become hard
to maintain. The initial design avoids such a registry.
Prototype status
The prototype is implemented as the two crates described above. It supports the
initial intrinsic tags, fluent attributes, expression children, conditional and
repeated children, dynamic elements, and structural namespaces.
Before proposing stabilization, the prototype should be exercised in several
real GPUI components and evaluated for:
readability compared with fluent builders;
compile time;
diagnostic quality;
formatter behavior;
source-location stability through macro expansion;
whether additional intrinsic tags are necessary.
Open questions
Should the public macro be named html!, ui!, or view!?
Should if without else be accepted as zero children?
Should dynamic tags apply source-derived scope automatically, or require an
explicit key/scoped marker?
Should bare string expressions receive an accessibility ID, or only bare
string literals and <text>?
Should intrinsic id reordering be generalized through metadata, or remain a
special case for the initial tags?
Should gpui_html use GPUI's release version immediately or remain an
independently versioned experiment?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
Add an optional HTML-like syntax layer for constructing GPUI element trees. The
syntax lowers directly to GPUI's existing fluent builder API and does not add a
virtual DOM, reconciliation, CSS runtime, or a second rendering model.
The feature lives outside the
gpuiandgpui_macroscrates:gpui_htmlis the public facade and contains small runtime helpers forstructural element identity.
gpui_html_macroscontains thehtml!procedural macro parser and codegenerator.
Applications opt in by depending on
gpui_html. GPUI itself does not depend oneither crate.
Motivation
GPUI's fluent element API is explicit and type-safe, but deeply nested UIs can
be difficult to scan:
The same structure can be represented without changing its runtime behavior:
The proposed syntax has three intended benefits:
.child(...)nesting is generated by the compiler.GPUI's Rust types continue to validate styles, events, and child elements.
Goals
model.
an error.
with interactive element IDs.
Non-goals
The proposal does not add:
onclick,src, oraria-label;Option,Result, or futures into elements;Crate organization
gpui_htmlgpui_htmlis a normal library crate that depends ongpuiandgpui_html_macros. It re-exportshtml!and owns runtime helpers that must useGPUI types.
The dependency direction is one-way:
gpuidoes not re-export the macro and does not depend ongpui_html.gpui_html_macrosgpui_html_macrosis a proc-macro crate. It depends onsyn,quote, andproc-macro2, but not ongpui.The macro resolves the facade's actual Cargo dependency name and generates all
runtime paths through
gpui_html, including a hidden re-export ofgpui. Thissupports renamed dependencies while keeping the parser independent from GPUI
runtime compilation and lets the syntax crate evolve or release separately.
Future Hooks
A future Hooks experiment should use another optional facade/runtime crate, for
example
gpui_hooks, with a matching proc-macro crate only if function-componentsyntax requires one. It should not be added to
gpui_htmlmerely because bothfeatures use a React/Yew-inspired surface syntax.
Separating them lets GPUI evaluate three decisions independently:
Proposed syntax
Intrinsic elements
The initial intrinsic registry is intentionally small:
<div>gpui::div()<svg>gpui::svg()<img source={value}>gpui::img(value)<text>{value}</text>gpui::text!(value)svgandimgare leaf elements.textaccepts exactly one string literal orbraced Rust expression.
Fluent attributes
Attribute names remain GPUI method names:
This lowers to the equivalent of:
Attribute forms are:
flexbecomes.flex();bg={value}becomes.bg(value);method(first, second)becomes.method(first, second).Attributes retain source order because GPUI's fluent API uses concrete and
stateful return types. Methods available only on the concrete element must appear
before
id; methods requiringStatefulInteractiveElement, such ason_click,must appear after
id. Reordering arbitrary methods in the macro would makeother valid chains, such as
svg().path(...).id(...), fail to compile.The macro does not maintain a complete method or event registry. Rust method
resolution and trait bounds remain the source of truth.
Text and expression children
A bare string literal lowers to
gpui::text!(literal)so it receives asource-derived accessibility ID. A braced expression may produce any
IntoElementvalue.Conditional and repeated children
The macro generates imperative
ParentElement::extendcalls. It does not createan
OptionElement, fragment element, or intermediate virtual tree.The initial syntax requires an
elsebranch forifandif let. This keepsthe parser and generated control flow explicit. Supporting an omitted branch can
be considered later.
Existing components and elements
The macro does not define a component props protocol. Existing GPUI values use a
dynamic tag:
The opening dynamic tag is
<@{expression}>, and a non-empty dynamic tag closeswith
</@>.A value that does not need additional attributes can also be inserted as an
expression child:
Expansion model
The macro produces ordinary Rust expressions. A parent with conditional children
is conceptually expanded as follows:
The root remains its concrete element type. Type erasure occurs only at the
existing
ParentElementchild boundary.Structural identity
GPUI's interactive
.id(...)participates in hit testing and accessibility. AReact-style key has a different purpose: it identifies a structural subtree.
The proposal therefore adds a wrapper owned by
gpui_html:NamespaceElementreturns the structural key fromElement::id()and delegateslayout, prepaint, and paint to its inner
AnyElement. It does not create a role,hitbox, focus target, or event listener.
key={value}expands togpui_html::keyed(value, element). An unkeyed dynamictag expands to
gpui_html::scoped(element), which uses the tag's source locationas a default namespace.
Source-derived scope distinguishes component instances written at different
locations. Repeated instances created at the same call site still require an
explicit stable key.
This is structural identity, not reconciliation. GPUI continues to rebuild the
element tree using its existing rendering model.
Diagnostics
The macro should diagnose errors it can identify without duplicating GPUI's type
system:
<@{expr}>;img.source;textchildren;idorkey;classor CSSstyleattributes;The generated tokens retain source spans for tags, attributes, and child
expressions. Rust reports unavailable fluent methods, invalid argument types,
event closure signature errors, and missing
IntoElementimplementations.Compatibility and adoption
The proposal requires no changes to existing GPUI applications. An application
opts in with:
Fluent builders and
html!can coexist in the same render function. The macroexpands to public GPUI APIs, so applications can inspect expansions with normal
Rust tooling.
The standalone crate should initially version independently from GPUI. If the
experiment proves stable, the crates may follow GPUI's release version without
moving their implementation into
gpuiorgpui_macros.Alternatives considered
Add
html!togpui_macrosThis reduces the number of packages but couples a comparatively large DSL parser
to GPUI's core derive macros and release cadence. It also forces all GPUI users
to compile code they may not use. Separate crates provide a clearer experimental
boundary.
Name the macro
ui!orview!Those names avoid suggesting browser compatibility.
html!is more immediatelyrecognizable to Yew users and code-generating tools. The crate documentation must
state that the syntax targets GPUI methods rather than HTML semantics. The final
name remains open for upstream discussion.
Add a virtual DOM
A virtual DOM could provide reconciliation and component lifecycle semantics,
but it would introduce a second tree and invalidate many assumptions in GPUI's
current element pipeline. It is not required to reduce builder nesting.
Generate fluent builder code with a declarative macro
A
macro_rules!implementation avoids a proc-macro crate but cannot provide thesame nested grammar, closing-tag validation, or source-focused diagnostics.
Risks
ordinary Rust method chains.
without
key.DSL; diagnostics for a misplaced
idmay come from Rust method resolution.html!may cause users to expect CSS and DOM behavior.to maintain. The initial design avoids such a registry.
Prototype status
The prototype is implemented as the two crates described above. It supports the
initial intrinsic tags, fluent attributes, expression children, conditional and
repeated children, dynamic elements, and structural namespaces.
Before proposing stabilization, the prototype should be exercised in several
real GPUI components and evaluated for:
Open questions
html!,ui!, orview!?ifwithoutelsebe accepted as zero children?explicit
key/scopedmarker?string literals and
<text>?idreordering be generalized through metadata, or remain aspecial case for the initial tags?
gpui_htmluse GPUI's release version immediately or remain anindependently versioned experiment?
All reactions