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." }
}
}
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" }
}
}
}
Text Content
Text content is placed directly inside curly braces after the element name:
html! {
p { "This is a paragraph with text content." }
}
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"
}
}
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 {
""
}
}
}
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
- The condition expression must be wrapped in
{}:if { condition } - The
elsebranch cannot be omitted. If you want nothing, useelse { "" } - Both branches must return valid content (either a
VirtualNodeor 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 {
""
}
}
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" }
}
}
}
}
Important Rules for match
- The match expression must be wrapped in
{}:match { expression } - Each arm must return valid content
- 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." }
}
}
}
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 }
}
}
}
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 }
}
}
}
Important Rules for for Loops
- The iterable expression must be wrapped in
{}:for item in { expression } - The loop body must return valid content
- 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 }
}
}
}
}
}
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"
}
}
}
}
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." }
}
}
}
}
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);
This example demonstrates:
-
Component definition:
todo_itemis a reusable component with typed props -
Conditional rendering: The list is shown/hidden based on
show_list -
List rendering:
forloop iterates over the items -
Event handling:
use_toggletoggles the boolean signal -
Component nesting:
TodoItemis used inside thehtml!macro
Summary
The html! macro is a powerful and flexible templating system:
-
Basic syntax: Nest elements with
{}, place text content directly, specify attributes withkey: value -
if/else: Conditional rendering with mandatory
elsebranch (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