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
The html! macro returns a value that implements Display(core::fmt::Display), so you can render it with
.to_string(), write!, or format!.
use html;
let name = "World";
let page = html! ;
assert_eq!;
The html! Macro
The html! macro provides a concise syntax for writing HTML:
use html;
let page = html! ;
assert_eq!;
DOCTYPE
Use #doctype to emit <!DOCTYPE html>:
use html;
let page = html! ;
assert!;
Expressions
Use parentheses to embed Rust expressions. Content is automatically HTML-escaped:
use html;
let user_input = "<script>alert('xss')</script>";
let html = html! ;
assert_eq!;
For raw (unescaped) content, prefix with #:
use html;
let trusted_html = "<strong>Bold</strong>";
let html = html! ;
assert_eq!;
Let Bindings
Use let bindings to compute intermediate values:
use html;
let world = " World";
let html = html! ;
assert_eq!;
Attributes
Attributes support several forms:
use html;
let class = Some;
let disabled = true;
let html = html! ;
assert_eq!;
Control Flow
Standard Rust control flow works naturally:
use html;
let show = true;
let items = vec!;
let variant = "primary";
let user = Some;
let html = html! ;
assert_eq!;
Nesting HTML Fragments
Use @(expr) to embed a value that implements HtmlDisplay (such as another html! fragment) without
escaping:
use html;
let inner = html! ;
let outer = html! ;
assert_eq!;
Ownership and Borrowing
html! expands into a move closure that implements Fn - it must be callable more than once (e.g. via
Display::fmt(core::fmt::Display::fmt)). Values used in the template are moved into the closure, but because
the closure is Fn, its captures live behind a shared reference and cannot be moved out.
For Copy types like &str, i32, or bool, this is invisible - they are copied each time the closure runs.
For owned types like String or Vec, you must explicitly borrow with & inside the template:
use html;
let name = Stringfrom;
// ERROR: cannot move `name` out of the `Fn` closure
let fragment = html! ;
Use & to borrow the captured value instead:
use html;
let name = Stringfrom;
let fragment = html! ;
assert_eq!;
The same applies anywhere a value is used inside the template - element children, attribute values, loop
iterators, etc. When in doubt, borrow with &.
Components
Create reusable components using the component! macro:
use ;
component!
let html = html! ;
assert_eq!;
Inside components, #attrs spreads additional HTML attributes passed at the call site and #children renders the
component's child content.
Passing HTML as Props
Components can accept html! fragments as props using the HtmlDisplay trait:
use ;
component!
let html = html! ;
assert_eq!;
Component Syntax
Components support generics, lifetimes, anonymous lifetimes, impl Trait parameters, and where clauses:
use ;
// Anonymous lifetimes: `&str` is automatically desugared
component!
// Explicit generics with where clauses
component!
Prop Access
Props are received as references inside the component body. Primitive types like bool and u32 should be
dereferenced with *:
use ;
component!
let html = html! ;
assert_eq!;
URL Safety
URL attributes (href, src, action, etc.) are automatically validated. Dangerous schemes like javascript:
are stripped:
use html;
let html = html! ;
assert_eq!; // href removed
Safe schemes (http, https, mailto, tel) and relative paths are allowed. Use #(...) for raw URLs when
you trust the source.
Merging CSS Classes
Use classes! to combine multiple class values into a single space-separated string. Any type implementing
ClassPart can be used - empty strings and None values are automatically skipped:
use ;
component!
let html = html! ;
assert_eq!;
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (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.