Skip to content

Add JSX/XML-like tag syntax to the rsx! macro - #5730

Open
nicoburns wants to merge 3 commits into
devin/1791038588-autofmt-comment-testsfrom
devin/1785758131-jsx-syntax
Open

nicoburns wants to merge 3 commits into
devin/1791038588-autofmt-comment-testsfrom
devin/1785758131-jsx-syntax

Conversation

@nicoburns

@nicoburns nicoburns commented Aug 3, 2026 •

Copy link
Copy Markdown
Member

Summary

Stacked on #5883. Adds a JSX/XML-like tag syntax to rsx!, freely mixable with the existing block syntax (similar to dioxus-html-macro, but integrated into the one macro). A < token switches BodyNode::parse into tag mode:

rsx! {
    <div class="container" onclick={move |_| info!("clicked")}>
        <h1>"Hello, {name}!"</h1>
        <img src="image.png" />
        <MyComponent prop="value" {..props}>"children"</MyComponent>

        // both syntaxes mix freely in either direction
        p { class: "regular", "regular child" }
        for item in items {
            <span>"{item}"</span>
        }
    </div>
}

The new parser (packages/rsx/src/jsx.rs) produces the same Element/Component AST as the block syntax, so templates, hot-reloading, attribute merging, and diagnostics behave identically. Element-vs-component tag names, string-literal text nodes, key, shorthand attributes (<div class>), custom/dashed attributes (data-foo="1", "custom-attr"="x"), web components (<my-widget />), component paths/generics (<some::Outlet<R>>, closing tag generics optional), and {..spread} all follow the existing rsx rules. Attribute values must be literals or braced expressions (attr={expr}).

Breaking change (dioxus-rsx): the brace: Option<Brace> field of Element and Component is replaced by delimiter: NodeDelimiter:

pub enum NodeDelimiter {
    Brace(Brace),        // div { }
    Tag(TagDelimiter),   // <div></div> / <div />: the real `<`, `/`, `>` and closing tag tokens
    Missing,             // div   (was `brace: None`)
}

BodyNode::first_token_span() returns the < for tags (BodyNode::span() still points at the name).

Error handling:

  • Tags that are still being typed are parsed as far as possible and reported as diagnostics instead of failing the macro, like the block syntax: unterminated open tags (<di, <div cl, <div class=), missing closing tags, and attribute value expressions that don't parse yet (class={foo.}, kept as raw tokens). An unterminated or unclosed tag does not swallow its siblings or its parent's closing tag.
  • A closing tag that matches neither the open tag nor one of its parents is still a hard error (closing tag `</dvi>` does not match opening tag `<div>` ).
  • Unquoted text gets a dedicated error (text in a tag must be a quoted string literal). A single word directly before a tag (<h1>Hello</h1>) is ambiguous with an element/component that is being typed, so it expands with a diagnostic that suggests quoting it.
  • Fragments (<>...</>) are rejected with a dedicated error.

Supporting changes:

  • Element::from_parts / Component::from_parts extracted from the existing Parse impls so both syntaxes share the merge/validation logic.
  • ExprNode::parse ends a {expr} or match node at a following <, so {children}</div> and match x { .. } </ul> parse. This is limited to nodes (which can never be comparisons); PartialExpr::parse is unchanged, so attribute values like hidden: {a} < b still parse as expressions.
  • autofmt: dx fmt rewrites tag syntax into the block syntax in this PR (Preserve JSX tag syntax in autofmt #5731 makes it preserve tags). The only autofmt change here is glue: the BodyDelimiters that fix(autofmt): stop dropping comments on attributes, spreads, control flow and empty macros #5883 introduced are built from a NodeDelimiter, mapping the tag tokens onto the open/close positions, so comments before/after tags, on attributes, and before closing tags survive the rewrite.

Tests: parser unit tests in jsx.rs (elements, components, generics, web components, spreads, shorthand, mixing, incomplete tags, unquoted text, error cases, codegen), SSR rendering tests in packages/ssr/tests/jsx_syntax.rs, a hot-reload test, and an autofmt test for comment preservation (tests/wrong/jsx-comments*).

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/99eedb36d44345dc8ce0f3ed5c460bd6
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/99eedb36d44345dc8ce0f3ed5c460bd6?variant=devin-insiders
Requested by: @nicoburns

@nicoburns nicoburns self-assigned this Aug 3, 2026
@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1785758131-jsx-syntax branch from ca9d7da to a269c40 Compare October 3, 2026 14:57
@staging-devin-ai-integration
staging-devin-ai-integration Bot removed this pull request from stack #5732 October 3, 2026 14:58
@staging-devin-ai-integration
staging-devin-ai-integration Bot changed the base branch from main to devin/1791038588-autofmt-comment-tests October 3, 2026 14:58
@staging-devin-ai-integration
staging-devin-ai-integration Bot added this pull request to stack #5884 October 3, 2026 14:58
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1785758131-jsx-syntax branch 2 times, most recently from 94763f6 to e84bdc8 Compare October 3, 2026 15:26
…d fix parser edge cases

- Replace the `brace: Option<Brace>` field of `Element` and `Component` with a
  `delimiter: NodeDelimiter` enum that holds either the brace of the block syntax or the
  real `<`, `>` and closing tag tokens of the tag syntax, instead of synthesizing a brace
- Parse incomplete tags (unterminated open tags, missing closing tags, incomplete
  attribute value expressions) with diagnostics instead of failing the whole macro
- Report unquoted text in a tag with a dedicated error
- Fix unbraced `match` nodes directly before a tag
- Only end braced expressions at a `<` for nodes, not for attribute values, so that
  `hidden: {a} < b` parses again
- autofmt: find the comments around tags using the tag tokens so that they are kept when
  tags are rewritten into the block syntax
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1785758131-jsx-syntax branch from e84bdc8 to 85ea1ff Compare October 3, 2026 15:28

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant