Skip to main content

html

Macro html 

Source
html!() { /* proc-macro */ }
Expand description

A procedural macro for generating type-safe HTML with embedded Rust expressions.

The html! macro provides a macro-based syntax for creating HTML content at compile time. It returns an HtmlFragment that implements IntoHtml and IntoHtmlRaw.

§Basic Usage

use plait::{html, render};

let html = render(html! {
    div {
        "Hello World"
    }
});
// Output: <div>Hello World</div>

§Syntax reference

§Text content

String literals are automatically HTML-escaped:

html! { "<script>alert('XSS')</script>" }
// Output: &lt;script&gt;alert('XSS')&lt;/script&gt;

§Expressions

Rust expressions can be embedded using parentheses. Values are HTML-escaped:

let name = "World";
html! { "Hello " (name) }
// Output: Hello World

§Raw (unescaped) output

Use #(expr) to output content without HTML escaping:

html! { #("<strong>Bold</strong>") }
// Output: <strong>Bold</strong>

Warning: Only use raw output with trusted content to prevent XSS vulnerabilities.

§Elements

HTML elements use a block syntax with the tag name followed by braces:

html! {
    div {
        span { "Nested content" }
    }
}
// Output: <div><span>Nested content</span></div>

§Element name conversion

Element names are automatically converted from snake_case to kebab-case:

html! { custom_element { "Content" } }
// Output: <custom-element>Content</custom-element>

§Void elements

Void elements (br, hr, img, input, meta, link, etc.) use a semicolon instead of braces:

html! {
    div {
        br;
        input(type: "text");
    }
}
// Output: <div><br><input type="text"></div>

§Attributes

Attributes are specified in parentheses after the element name:

html! {
    div(class: "container", id: "main") {
        "Content"
    }
}
// Output: <div class="container" id="main">Content</div>

§Attribute name conversion

Attribute names with underscores are converted to hyphens:

html! { div(hx_target: "body") {} }
// Output: <div hx-target="body"></div>

§String literal attribute names

Use string literals for attribute names with special characters:

html! { div("@click": "handleClick()") {} }
// Output: <div @click="handleClick()"></div>

§Boolean attributes

Attributes without values render as boolean attributes:

html! { button(disabled) { "Submit" } }
// Output: <button disabled>Submit</button>

§Optional attributes

Use ?: syntax for conditional attributes. Works with Option<T> and bool:

let class = Some("active");
let disabled = false;

html! {
    button(class?: class, disabled?: disabled) {
        "Click"
    }
}
// Output: <button class="active">Click</button>

§Raw attribute values

Use #(expr) for unescaped attribute values:

html! { div(class: #("<script>")) {} }
// Output: <div class="<script>"></div>

§URL attribute protection

URL attributes (href, src, action, etc.) are automatically validated. Dangerous protocols like javascript: are stripped:

html! { a(href: "javascript:alert('XSS')") { "Link" } }
// Output: <a>Link</a>

// Safe URLs work normally:
html! { a(href: "https://example.com") { "Link" } }
// Output: <a href="https://example.com">Link</a>

Use #(expr) to bypass URL validation when needed (trusted content only).

§Control flow

§If expressions

let show = true;

html! {
    if show {
        div { "Visible" }
    }
}

With else and else if:

let count = 5;

html! {
    if count == 0 {
        "None"
    } else if count == 1 {
        "One"
    } else {
        "Many"
    }
}

§If let expressions

let user = Some("Alice");

html! {
    if let Some(name) = user {
        span { "Hello, " (name) }
    } else {
        span { "Guest" }
    }
}

§For loops

let items = vec!["Apple", "Banana", "Cherry"];

html! {
    ul {
        for item in items {
            li { (item) }
        }
    }
}
// Output: <ul><li>Apple</li><li>Banana</li><li>Cherry</li></ul>

§Match expressions

let status = "success";

html! {
    match status {
        "success" => span(class: "green") { "OK" },
        "error" => span(class: "red") { "Failed" },
        _ => span { "Unknown" }
    }
}

§Component calls

Components are invoked with @ComponentName syntax. See the component! macro for defining components.

html! {
    @Button(class: "primary"; id: "submit", disabled?: false) {
        "Click me"
    }
}

Component calls use a semicolon to separate component props from HTML attributes:

  • Before ;: Component-specific props
  • After ;: HTML attributes passed through #attrs

§Generated code

The macro generates an HtmlFragment which implements IntoHtml. Use with render() or render_with_capacity() to produce the final HTML string, or pass fragments directly to component props that accept T: IntoHtml.