Skip to main content

Quick Start

In this tutorial, you’ll build your first interactive Dioxus application. We’ll create a counter app with multiple features to demonstrate the core concepts of Dioxus development.
Before starting, make sure you’ve completed the Installation guide and have the Dioxus CLI installed.

Create Your First App

Let’s start by creating a new Dioxus project:
1

Create a new project

Run the following command in your terminal:
dx new my-counter-app
You’ll be prompted to choose a template. Select Web App for this tutorial.
2

Navigate to your project

cd my-counter-app
3

Start the development server

dx serve
This will compile your app and open it in your default browser. You should see a welcome message!

Understanding the Basics

Open src/main.rs in your editor. You’ll see something like this:
use dioxus::prelude::*;

fn main() {
    dioxus::launch(app);
}

fn app() -> Element {
    rsx! {
        div { "Hello, world!" }
    }
}
Let’s break this down:
use dioxus::prelude::*;
This imports all the commonly used Dioxus types and macros, including Element, rsx!, component macros, and more.
fn main() {
    dioxus::launch(app);
}
The launch function starts your Dioxus application. It takes a root component function (in this case, app) and renders it using the appropriate renderer for your platform.
fn app() -> Element {
    rsx! {
        div { "Hello, world!" }
    }
}
Components are functions that return Element. The rsx! macro lets you write HTML-like syntax that compiles to Rust code.

Building a Counter App

Now let’s build something interactive! Replace the contents of src/main.rs with:
use dioxus::prelude::*;

fn main() {
    dioxus::launch(app);
}

fn app() -> Element {
    // Create a reactive state variable
    let mut count = use_signal(|| 0);

    rsx! {
        div {
            h1 { "Counter: {count}" }
            button { 
                onclick: move |_| count += 1,
                "Increment" 
            }
            button { 
                onclick: move |_| count -= 1,
                "Decrement" 
            }
        }
    }
}
Save the file and watch your browser update automatically with hot-reload!

What’s New?

State with Signals

let mut count = use_signal(|| 0);
use_signal creates reactive state. The closure || 0 initializes the value to 0. The mut keyword allows us to modify the signal.

Event Handlers

onclick: move |_| count += 1
Event handlers are closures that run when events occur. We use move to capture variables and += to modify the signal.

Interpolation

h1 { "Counter: {count}" }
Use curly braces {} to embed Rust expressions in your UI. When count changes, this text updates automatically.

RSX Syntax

button { onclick: handler, "Text" }
Elements are written like function calls. Attributes come first (onclick), then children (“Text”).

Adding More Features

Let’s enhance our counter with additional functionality:
use dioxus::prelude::*;

fn main() {
    dioxus::launch(app);
}

fn app() -> Element {
    let mut count = use_signal(|| 0);
    
    // Computed value that's automatically updated
    let doubled = use_memo(move || count() * 2);
    
    // Side effect that runs when count changes
    use_effect(move || {
        println!("Count changed to: {}", count());
    });

    rsx! {
        div { 
            style: "font-family: Arial; padding: 20px;",
            
            h1 { "High-Five Counter" }
            
            div { 
                style: "font-size: 24px; margin: 20px 0;",
                "Current count: {count}" 
            }
            
            div {
                style: "font-size: 18px; color: #666; margin-bottom: 20px;",
                "Doubled: {doubled}"
            }
            
            div { 
                style: "display: flex; gap: 10px;",
                
                button { 
                    onclick: move |_| count += 1,
                    "Up high! 🙌" 
                }
                button { 
                    onclick: move |_| count -= 1,
                    "Down low! 👇" 
                }
                button {
                    onclick: move |_| count.set(0),
                    "Reset 🔄"
                }
            }
            
            // Conditional rendering
            if count() > 10 {
                div { 
                    style: "color: green; margin-top: 20px; font-weight: bold;",
                    "Wow, that's a lot of high fives! 🎉" 
                }
            }
            
            if count() < 0 {
                div { 
                    style: "color: red; margin-top: 20px; font-weight: bold;",
                    "Going negative? That's low! 😅" 
                }
            }
        }
    }
}

New Concepts Introduced

let doubled = use_memo(move || count() * 2);
use_memo creates a computed value that automatically updates when its dependencies change. It’s only recalculated when count changes, making it efficient.
Use memos for expensive computations or derived state to avoid unnecessary recalculations.

Creating Reusable Components

Let’s extract our counter controls into a reusable component:
use dioxus::prelude::*;

fn main() {
    dioxus::launch(app);
}

fn app() -> Element {
    let mut count = use_signal(|| 0);

    rsx! {
        div { style: "font-family: Arial; padding: 20px;",
            h1 { "High-Five Counter" }
            
            CounterDisplay { value: count() }
            
            CounterControls { 
                count: count,
            }
        }
    }
}

// Display component with props
#[component]
fn CounterDisplay(value: i32) -> Element {
    let doubled = value * 2;
    
    rsx! {
        div { style: "margin: 20px 0;",
            div { 
                style: "font-size: 24px; font-weight: bold;",
                "Current count: {value}" 
            }
            div {
                style: "font-size: 18px; color: #666;",
                "Doubled: {doubled}"
            }
        }
    }
}

// Controls component that modifies state
#[component]
fn CounterControls(count: Signal<i32>) -> Element {
    rsx! {
        div { style: "display: flex; gap: 10px;",
            button { 
                onclick: move |_| count += 1,
                "Up high! 🙌" 
            }
            button { 
                onclick: move |_| count -= 1,
                "Down low! 👇" 
            }
            button {
                onclick: move |_| count.set(0),
                "Reset 🔄"
            }
        }
    }
}

Component Props

#[component]
fn CounterDisplay(value: i32) -> Element { ... }
The #[component] macro automatically creates a props struct from your function parameters. It’s not required but highly recommended for better error messages and auto-completion.
CounterDisplay { value: count() }
Props are passed using the component name followed by key-value pairs. The syntax is similar to struct initialization.
CounterControls { count: count }
You can pass signals directly to child components. This allows children to read and modify parent state.
For read-only access, use ReadSignal<T> as the parameter type instead of Signal<T>.

Working with Lists

Let’s create a multi-counter app that manages a list of counters:
use dioxus::prelude::*;

fn main() {
    dioxus::launch(app);
}

fn app() -> Element {
    let mut counters = use_signal(|| vec![0, 0, 0]);
    let sum = use_memo(move || counters.read().iter().sum::<i32>());

    rsx! {
        div { style: "font-family: Arial; padding: 20px;",
            h1 { "Multiple Counters" }
            
            div { style: "margin: 20px 0;",
                button { 
                    onclick: move |_| counters.push(0),
                    "➕ Add Counter" 
                }
                button { 
                    onclick: move |_| { counters.pop(); },
                    "➖ Remove Counter" 
                }
            }
            
            h3 { "Total: {sum}" }
            
            // Iterate over counters
            for (i, counter) in counters.iter().enumerate() {
                div { 
                    key: "{i}",
                    style: "display: flex; gap: 10px; align-items: center; margin: 10px 0;",
                    
                    span { "Counter {i + 1}:" }
                    
                    button { 
                        onclick: move |_| counters.write()[i] -= 1,
                        "−" 
                    }
                    
                    span { 
                        style: "font-weight: bold; min-width: 40px; text-align: center;",
                        "{counter}" 
                    }
                    
                    button { 
                        onclick: move |_| counters.write()[i] += 1,
                        "+" 
                    }
                    
                    button {
                        onclick: move |_| { counters.remove(i); },
                        "🗑️"
                    }
                }
            }
        }
    }
}

List Rendering Concepts

1

Store collections in signals

let mut counters = use_signal(|| vec![0, 0, 0]);
Vectors, HashMaps, and other collections can be stored in signals. When the collection changes, the UI updates.
2

Iterate with for loops

for (i, counter) in counters.iter().enumerate() {
    div { key: "{i}", ... }
}
Use regular Rust for loops to iterate. Calling .iter() on a Signal<Vec<T>> gives you reactive access to each item.
3

Add unique keys

div { key: "{i}", ... }
Keys help Dioxus identify which items changed, improving rendering efficiency.
In production apps, use stable IDs instead of indices for keys. Indices can cause issues when items are reordered.
4

Modify collections

counters.write()[i] += 1  // Modify item
counters.push(0)          // Add item
counters.remove(i)        // Remove item
Use .write() to get mutable access to the collection, then use standard Vec methods.

Next Steps

Congratulations! You’ve built your first Dioxus apps and learned the fundamentals:

✅ Components

Creating reusable UI functions with props

✅ State Management

Using signals for reactive state

✅ Event Handling

Responding to user interactions

✅ List Rendering

Working with collections and iteration

Where to Go Next

Styling Your App

Learn about CSS, Tailwind, and styling best practices

Routing

Add navigation and multiple pages to your app

Async & Data Fetching

Work with APIs and asynchronous operations

Full-Stack Apps

Add server functions and backend logic

Pro Tips

Hot-reload is your friend: Make changes and see them instantly. The RSX hot-reload works for markup and styles, while the experimental --hot-patch flag enables Rust code hot-reload.
Use the community: Join the Dioxus Discord to ask questions, share your projects, and learn from others.
Check out examples: The Dioxus GitHub repository has dozens of examples covering advanced topics like routing, server functions, animations, and platform-specific features.
Read the docs: The official Dioxus guide provides in-depth coverage of all features.

Common Patterns

Reset State

button { 
    onclick: move |_| count.set(0),
    "Reset" 
}

Toggle Boolean

let mut enabled = use_signal(|| false);

button { 
    onclick: move |_| enabled.toggle(),
    "Toggle" 
}

Input Binding

let mut text = use_signal(|| String::new());

input {
    value: "{text}",
    oninput: move |e| text.set(e.value()),
}

Async Operations

let mut data = use_signal(|| None);

use_effect(move || {
    spawn(async move {
        let result = fetch_data().await;
        data.set(Some(result));
    });
});

Build docs developers (and LLMs) love