# plait
A lightweight, type-safe HTML templating library for Rust.
Plait provides a macro-based DSL for writing HTML directly in Rust code with compile-time validation and automatic
escaping. It's designed for building server-side rendered HTML with minimal runtime overhead.
## Quick Start
```rust
use plait::{html, render};
let name = "World";
let html = render(html! {
div(class: "greeting") {
h1 { "Hello, " (name) "!" }
}
});
assert_eq!(html, "<div class=\"greeting\"><h1>Hello, World!</h1></div>");
```
## The `html!` Macro
The `html!` macro provides a concise syntax for writing HTML:
```rust
use plait::{html, render};
let html = render(html! {
// Elements with attributes
div(id: "main", class: "container") {
// Nested elements
h1 { "Title" }
// Self-closing void elements
br;
input(type: "text", name: "query");
// Text content and expressions (escaped by default)
p { "Static text and " (2 + 2) " dynamic values" }
}
});
```
### Expressions
Use parentheses to embed expressions. Content is automatically HTML-escaped:
```rust
use plait::{html, render};
let user_input = "<script>alert('xss')</script>";
let html = render(html! { p { (user_input) } });
assert_eq!(html, "<p><script>alert('xss')</script></p>");
```
For raw, unescaped content, prefix with `#`:
```rust
use plait::{html, render};
let trusted_html = "<strong>Bold</strong>";
let html = render(html! { div { #(trusted_html) } });
assert_eq!(html, "<div><strong>Bold</strong></div>");
```
### Attributes
Attributes support several forms:
```rust
use plait::{html, render};
let class = Some("active");
let disabled = true;
let html = render(html! {
// Boolean attribute (no value)
button(checked) { "Checked" }
// Optional attribute with `?:` - included when Some or true
div(class?: class) { "Has class" }
button(disabled?: disabled) { "Disabled" }
// Attribute names with underscores become hyphens
div(hx_target: "body") {} // renders as hx-target="body"
// String attribute names for special characters
div("@click": "handler()") {}
});
```
### Control Flow
Standard Rust control flow works naturally:
```rust
use plait::{html, render};
let show = true;
let items = vec!["a", "b", "c"];
let variant = "primary";
let user = Some("Alice");
let html = render(html! {
// Conditionals
if show {
p { "Visible" }
} else {
p { "Hidden" }
}
// if let with else
if let Some(name) = user {
p { "Welcome, " (name) "!" }
} else {
p { "Please log in" }
}
// Loops
ul {
for item in &items {
li { (item) }
}
}
// Pattern matching
match variant {
"primary" => button(class: "btn-primary") { "Primary" },
"secondary" => button(class: "btn-secondary") { "Secondary" },
_ => button { "Default" }
}
});
```
## Components
Create reusable components using the `component!` macro:
```rust
use plait::{component, html, render};
component! {
fn Button<'a>(class: &'a str) {
button(class: format_args!("btn {class}"), #attrs) {
#children
}
}
}
let html = render(html! {
// Component props before `;`, HTML attributes after
@Button(class: "primary"; id: "submit-btn", disabled?: false) {
"Click me"
}
});
assert_eq!(html, "<button class=\"btn primary\" id=\"submit-btn\">Click me</button>");
```
Inside components, `#attrs` spreads additional HTML attributes and `#children` renders the component's children.
## URL Safety
URL attributes (`href`, `src`, `action`, etc.) are automatically validated. Dangerous schemes like `javascript:`
are stripped:
```rust
use plait::{html, render};
let html = render(html! {
a(href: "javascript:alert('xss')") { "Click" }
});
assert_eq!(html, "<a>Click</a>"); // href removed
```
Use `#(...)` for raw URLs when you trust the source.
## Performance
For better performance when output size is predictable, use `render_with_capacity` to pre-allocate the buffer:
```rust
use plait::{html, render_with_capacity};
let html = render_with_capacity(4096, html! {
div { "Content" }
});
```
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as
defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.