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