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: <script>alert('XSS')</script>§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.