DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

The html! Macro in Depth

Project Code:https://github.com/euv-dev/euv

Introduction to the html! Macro

The html! macro is the heart of euv's templating system. It provides a declarative, HTML-like syntax directly within Rust code, allowing you to construct virtual DOM nodes using a familiar structure. The macro compiles into efficient VirtualNode constructions, giving you the best of both worlds: the readability of HTML and the type safety of Rust.

The basic syntax is intuitive:

html! {
    div {
        h1 { "Hello, euv!" }
        p { "Welcome to the html! macro." }
    }
}
Enter fullscreen mode Exit fullscreen mode

This produces a VirtualNode tree with a div element containing an h1 and a p element.

Basic Syntax Rules

Nesting Elements

Elements are nested using curly braces. Each element can contain child elements or text content:

html! {
    div {
        header {
            nav {
                a { href: "/home" "Home" }
            }
        }
        main {
            p { "Content here" }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Text Content

Text content is placed directly inside curly braces after the element name:

html! {
    p { "This is a paragraph with text content." }
}
Enter fullscreen mode Exit fullscreen mode

Attributes

Attributes are specified using the key: value syntax inside the element's curly braces, before any child content:

html! {
    input {
        type: "text",
        placeholder: "Enter your name"
    }
}
Enter fullscreen mode Exit fullscreen mode

Conditional Rendering with if/else

The html! macro supports conditional rendering using if/else blocks. The condition must be wrapped in curly braces, and the else branch is required — if you want nothing to display, use an empty string "":

html! {
    div {
        if { show.get() } {
            div { "Visible" }
        } else {
            ""
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the div with "Visible" text is only rendered when show.get() returns true. Otherwise, an empty string is rendered.

Important Rules for if/else

  1. The condition expression must be wrapped in {}: if { condition }
  2. The else branch cannot be omitted. If you want nothing, use else { "" }
  3. Both branches must return valid content (either a VirtualNode or a string)
// Correct: else branch present
html! {
    if { is_logged_in.get() } {
        div { "Welcome back!" }
    } else {
        div { "Please log in." }
    }
}

// Correct: empty else branch
html! {
    if { show_banner.get() } {
        div { "Special offer!" }
    } else {
        ""
    }
}
Enter fullscreen mode Exit fullscreen mode

Conditional Rendering with match

For more complex conditional logic, the html! macro supports match expressions. A match block must include a wildcard _ branch to handle all remaining cases:

html! {
    div {
        match { user_type.get().as_str() } {
            "guest" => {
                div { "Guest" }
            }
            "admin" => {
                div { "Admin" }
            }
            _ => {
                div { "Unknown" }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Important Rules for match

  1. The match expression must be wrapped in {}: match { expression }
  2. Each arm must return valid content
  3. A wildcard _ branch is required to handle all unmatched cases
html! {
    match { status.get().as_str() } {
        "loading" => {
            div { "Loading..." }
        }
        "success" => {
            div { "Data loaded successfully!" }
        }
        "error" => {
            div { "An error occurred." }
        }
        _ => {
            div { "Unknown status." }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

List Rendering with for Loops

The html! macro supports for loops for rendering lists of items. The iterable expression must be wrapped in curly braces:

html! {
    ul {
        for item in { items.get() } {
            li { item }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Iterating with Index

You can also iterate with an index using .iter().enumerate():

html! {
    ol {
        for (index, item) in { items.get().iter().enumerate() } {
            li { index item }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Important Rules for for Loops

  1. The iterable expression must be wrapped in {}: for item in { expression }
  2. The loop body must return valid content
  3. You can destructure the iterator, e.g., for (index, item) in { ... }
html! {
    table {
        tbody {
            for (index, item) in { users.get().iter().enumerate() } {
                tr {
                    td { index }
                    td { item }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Reactive Attribute Values

The html! macro supports reactive attribute values. When an attribute depends on a signal, the attribute automatically updates when the signal changes:

fn app() -> VirtualNode {
    let count: Signal<i32> = use_signal(|| 0);

    html! {
        div {
            h1 { "Counter" }
            button {
                onclick: move |event: Event| {
                    count.set(count.get() + 1);
                }
                "Increment"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the button's onclick handler reads and updates the count signal. When the signal changes, euv automatically re-renders the affected parts of the UI.

Nesting Components

The html! macro supports nesting custom components inside the template. Components are referenced by their function name:

#[derive(Clone, Default)]
struct MyCardProps {
    title: &'static str,
}

#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
    let MyCardProps { title, .. } = node.try_get_props().unwrap_or_default();
    let children: VirtualNode = node.try_get_child_node();
    html! {
        div {
            h3 { title }
            children
        }
    }
}

fn app() -> VirtualNode {
    html! {
        div {
            MyCard {
                title: "Welcome"
                p { "This is content inside a card." }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Components can receive props as named attributes and children as nested content. The component function receives a VirtualNode that carries both the props and child nodes.

Combining All Features

Here's a more comprehensive example that combines conditional rendering, list rendering, and component nesting:

use euv::*;

#[derive(Clone, Default)]
struct TodoItemProps {
    text: &'static str,
}

#[component]
pub fn todo_item(node: VirtualNode<TodoItemProps>) -> VirtualNode {
    let TodoItemProps { text, .. } = node.try_get_props().unwrap_or_default();
    html! {
        li { text }
    }
}

fn app() -> VirtualNode {
    let items: Signal<Vec<String>> = use_signal(|| vec![
        "Learn euv".to_string(),
        "Build an app".to_string(),
    ]);
    let show_list: Signal<bool> = use_signal(|| true);

    html! {
        div {
            h1 { "My Todo List" }

            if { show_list.get() } {
                ul {
                    for item in { items.get() } {
                        TodoItem {
                            text: item
                        }
                    }
                }
            } else {
                p { "List is hidden." }
            }

            button {
                onclick: use_toggle(show_list)
                "Toggle List"
            }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Component definition: todo_item is a reusable component with typed props
  • Conditional rendering: The list is shown/hidden based on show_list
  • List rendering: for loop iterates over the items
  • Event handling: use_toggle toggles the boolean signal
  • Component nesting: TodoItem is used inside the html! macro

Summary

The html! macro is a powerful and flexible templating system:

  • Basic syntax: Nest elements with {}, place text content directly, specify attributes with key: value
  • if/else: Conditional rendering with mandatory else branch (use "" for empty)
  • match: Multi-branch conditional rendering with required _ wildcard
  • for loops: List rendering with { } wrapped iterables, supports index via .enumerate()
  • Reactive attributes: Attributes automatically update when signals change
  • Component nesting: Use custom components directly in the template with props and children

The html! macro, combined with euv's reactive signals and component system, provides everything you need to build complex, interactive user interfaces in Rust.


Project Code:https://github.com/euv-dev/euv

Top comments (0)