Skip to main content
The rsx! macro is Dioxus’s JSX-like syntax for declaring user interfaces. It provides a compile-time checked, ergonomic way to build UI trees.

Basic Syntax

RSX uses a brace-based syntax similar to JSX/HTML:
use dioxus::prelude::*;

rsx! {
    div {
        h1 { "Hello, World!" }
        p { "Welcome to Dioxus" }
    }
}

Elements

Create HTML elements by name:
rsx! {
    div {
        button { "Click me" }
        input { r#type: "text" }
        a { href: "https://dioxuslabs.com", "Visit Dioxus" }
    }
}

Web Components

Elements with a hyphen are treated as web components:
rsx! {
    custom-element {
        "data-value": "123",
        "Content"
    }
}

Attributes

Attributes are key-value pairs separated by colons:
let width = 100;

rsx! {
    div {
        class: "container",
        id: "main",
        width: "{width}px",
        style: "color: red;",
    }
}

Raw Attributes

For non-standard attributes, use quoted strings:
rsx! {
    div {
        "data-custom": "value",
        "aria-label": "Description",
    }
}

Optional Attributes

Use unterminated if statements for conditional attributes:
let is_active = true;
let should_highlight = false;

rsx! {
    div {
        class: if is_active { "active" },
        class: if should_highlight { "highlight" },
    }
}

Text and Interpolation

Static Text

rsx! {
    p { "This is static text" }
}

String Interpolation

Embed variables using curly braces:
let name = "Alice";
let count = 42;

rsx! {
    div {
        "Hello, {name}!"
        "Count: {count}"
    }
}

Formatted Text

Use format_args! for complex formatting:
let price = 19.99;

rsx! {
    p { "Price: ${price:.2}" }
    p { {format_args!("Total: ${:.2}", price * 1.1)} }
}

Components

Render components by name (must start with capital letter or contain _):
#[component]
fn Greeting(name: String) -> Element {
    rsx! { "Hello, {name}!" }
}

rsx! {
    div {
        Greeting { name: "World" }
        Greeting { name: "Dioxus" }
    }
}

Component Paths

rsx! {
    // Absolute path
    crate::components::Button { text: "Click" }
    
    // Relative path  
    self::Button { text: "Click" }
    
    // Module path
    ui::Button { text: "Click" }
}

Conditionals

If Statements

Use if/else for conditional rendering:
let show_message = true;
let count = 5;

rsx! {
    if show_message {
        p { "Message is visible" }
    }
    
    if count > 10 {
        p { "Count is high" }
    } else {
        p { "Count is low" }
    }
}

Match Expressions

enum Status {
    Loading,
    Success,
    Error,
}

let status = Status::Success;

rsx! {
    match status {
        Status::Loading => rsx! { p { "Loading..." } },
        Status::Success => rsx! { p { "Success!" } },
        Status::Error => rsx! { p { "Error!" } },
    }
}

Optional Rendering

rsx! {
    // Using .then()
    {true.then(|| rsx! { p { "Shown" } })}
    
    // Using Option
    {Some(rsx! { p { "Content" } })}
    
    // Empty fragments
    Fragment {}
}

Loops

For Loops

Iterate over collections:
let items = vec!["Apple", "Banana", "Cherry"];

rsx! {
    ul {
        for item in items {
            li { "{item}" }
        }
    }
}

With Keys

Provide keys for stable identity:
let users = vec![
    (1, "Alice"),
    (2, "Bob"),
];

rsx! {
    for (id, name) in users {
        div {
            key: "{id}",
            "User: {name}"
        }
    }
}

Iterator Methods

rsx! {
    {(0..10).map(|i| rsx! {
        li { key: "{i}", "Item {i}" }
    })}
}

Raw Expressions

Embed Rust expressions directly:
let count = 5;
let message = "Hello";

rsx! {
    div {
        // Variables
        {message}
        
        // Computations
        {count * 2}
        
        // Method calls
        {message.to_uppercase()}
        
        // Closures
        {(|| "computed value")()}
    }
}
Expressions must implement IntoDynNode trait. Supported types include:
  • String, &str
  • Numbers (i32, f64, etc.)
  • Element
  • Iterators of Element
  • Option<impl IntoDynNode>

Fragments

Group elements without a parent wrapper:
rsx! {
    Fragment {
        h1 { "Title" }
        p { "Paragraph 1" }
        p { "Paragraph 2" }
    }
}
Fragments can be nested and used in loops:
rsx! {
    for i in 0..3 {
        Fragment {
            key: "{i}",
            p { "Item {i}" }
            hr {}
        }
    }
}

Event Handlers

Handle events with the on* attributes:
let mut count = use_signal(|| 0);

rsx! {
    button {
        onclick: move |event| {
            println!("Button clicked!");
            count += 1;
        },
        "Clicks: {count}"
    }
    
    input {
        oninput: move |event| {
            println!("Input: {}", event.value());
        },
    }
}

Dangerous Inner HTML

Set raw HTML (use with caution):
rsx! {
    div {
        dangerous_inner_html: "<p>Raw <strong>HTML</strong></p>"
    }
}
Using dangerous_inner_html can expose your application to XSS attacks. Only use it with trusted content.

Complete Example

use dioxus::prelude::*;

#[component]
fn TodoList() -> Element {
    let mut todos = use_signal(|| vec![
        (1, "Learn Dioxus", false),
        (2, "Build an app", false),
    ]);
    let mut input = use_signal(|| String::new());
    
    rsx! {
        div { class: "todo-app",
            h1 { "My Todos" }
            
            div { class: "input-group",
                input {
                    value: "{input}",
                    oninput: move |e| input.set(e.value()),
                    placeholder: "Add a todo...",
                }
                button {
                    onclick: move |_| {
                        let id = todos.len() + 1;
                        todos.push((id, input().clone(), false));
                        input.set(String::new());
                    },
                    "Add"
                }
            }
            
            ul { class: "todo-list",
                for (id, text, completed) in todos.read().iter() {
                    li {
                        key: "{id}",
                        class: if *completed { "completed" } else { "" },
                        span { "{text}" }
                    }
                }
            }
            
            if todos.read().is_empty() {
                p { class: "empty", "No todos yet!" }
            }
        }
    }
}

See Also

  • Components - Create reusable UI components
  • Props - Pass data to components
  • State - Manage application state

Build docs developers (and LLMs) love