plait 0.2.0

A modern HTML templating library for Rust that embraces composition.
Documentation

plait

A fast, type-safe HTML templating library for Rust.

Plait provides compile-time HTML generation with a concise, Rust-native syntax. Templates are checked at compile time and generate efficient code with minimal runtime overhead.

Quick Start

use plait::{html, render};

let name = "World";
let template = html! {
    div class="greeting" {
        h1 { "Hello, " (name) "!" }
    }
};

let html = render(template);
assert_eq!(html, r#"<div class="greeting"><h1>Hello, World!</h1></div>"#);

Macros

Plait provides two macros:

  • html! - Creates a RenderFn from a template, which can be rendered using render()
  • attrs! - Creates an Attributes collection for use in templates

Template Syntax

Elements

Elements are written as name { children } for normal elements or name; for void elements:

use plait::{html, render};

let template = html! {
    div {
        p { "A paragraph" }
        br;
        input type="text" name="field";
    }
};

assert_eq!(render(template), r#"<div><p>A paragraph</p><br><input type="text" name="field"></div>"#);

Attributes

Attributes support several value types:

use plait::{html, render};

let class_name = "container";
let maybe_id: Option<&str> = Some("main");
let is_disabled = true;

let template = html! {
    div
        class="literal"              // Literal string
        data-value=(class_name)      // Dynamic expression
        id=[maybe_id]                // Optional (renders if Some)
        disabled?[is_disabled]       // Boolean (renders if true)
    {
        "content"
    }
};

assert_eq!(render(template), r#"<div class="literal" data-value="container" id="main" disabled>content</div>"#);

Dynamic Content

Expressions in parentheses are escaped by default:

use plait::{html, render};

let user_input = "<script>alert('xss')</script>";

let template = html! {
    div { (user_input) }
};

// Content is safely escaped
assert!(!render(template).contains("<script>"));

Use : raw to include pre-escaped content:

use plait::{html, render};

let trusted_html = "<strong>Bold</strong>";

let template = html! {
    div { (trusted_html : raw) }
};

assert!(render(template).contains("<strong>"));

Control Flow

Conditionals

use plait::{html, render};

let show = true;
let value: Option<&str> = Some("hello");

let template = html! {
    div {
        @if show {
            span { "Visible" }
        }

        @if let Some(v) = value {
            span { (v) }
        } @else {
            span { "No value" }
        }
    }
};

assert_eq!(render(template), r#"<div><span>Visible</span><span>hello</span></div>"#);

Loops

use plait::{html, render};

let items = vec!["one", "two", "three"];

let template = html! {
    ul {
        @for item in &items {
            li { (item) }
        }
    }
};

assert_eq!(render(template), r#"<ul><li>one</li><li>two</li><li>three</li></ul>"#);

Match Expressions

use plait::{html, render};

enum Status { Active, Inactive }
let status = Status::Active;

let template = html! {
    span {
        @match status {
            Status::Active => "Online",
            Status::Inactive => "Offline",
        }
    }
};

assert_eq!(render(template), r#"<span>Online</span>"#);

Custom Components

Create reusable components as functions that return impl Render:

use plait::{Render, html, render};

fn button(label: &str, primary: bool) -> impl Render {
    let class = if primary { "btn btn-primary" } else { "btn" };
    html! {
        button class=(class) { (label) }
    }
}

// Use in templates
let template = html! {
    div { (button("Click me", true)) }
};

assert_eq!(render(template), r#"<div><button class="btn btn-primary">Click me</button></div>"#);

Safety

Plait automatically escapes dynamic content to prevent XSS vulnerabilities. The Html and PreEscaped types represent content that is already safe and will not be escaped again.

License

Licensed under either of

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.