Compile-time template engine for Rust

HTML templates that compile to Rust

Write components in .wtz files. Waltzing turns them into type-safe Rust functions at build time — escaped by default, checked by the compiler, with zero runtime template parsing.

curl -fsSL https://waltzing.awesomike.com/install | bash

greeting.wtz
@fn greeting(name: &str, admin: bool) {
    <h1>Hello, @name 👋</h1>
    @if admin {
        <span class="badge">Admin</span>
    }
}
Type-safeErrors caught at compile time, not in production
Escaped by defaultXSS-safe output without thinking about it
Native speedNo runtime parsing — it's just Rust
Component-basedComposable, reusable, JSX-like

Your templates are Rust

Every @fn becomes a real function. Parameters are typed, expressions are Rust, and a mistake is a cargo build error — never a 500 at runtime.

price_tag.wtz
@fn price_tag(label: &str, cents: i64) {
    <div class="tag">
        <span>@label</span>
        <b>@currency(cents as f64 / 100.0, "USD")</b>
    </div>
}
generated Rust
pub fn price_tag(label: &str, cents: i64)
    -> Content<'static>
{
    let mut __t = String::new();
    __t.push_str("<div class=\"tag\"><span>");
    __t.push_str(&rt::escape::html(label));   // auto-escaped
    __t.push_str("</span><b>");
    currency(cents as f64 / 100.0, "USD").write(&mut __t);
    __t.push_str("</b></div>");
    Content::from(__t)
}
// A type error in the template is a compile error here.

Everything a web layer needs

Components & composition

Import templates as modules and call them with JSX-like function tags. Default parameters and content slots keep them flexible.

@import /components/card.wtz as card

@fn dashboard(user: &User, stats: &[Stat]) {
    <@card::apply title="Welcome back">
        <p>Signed in as @user.email</p>
    </@>

    @for s in stats {
        <@card::apply title=@&s.label>
            <strong>@s.value</strong>
        </@>
    }
}

Real control flow & safe output

Native @if, @for, and @match over real Rust. Text is auto-escaped and safe_url blocks dangerous schemes.

@fn comment(author: &str, body: &str, site: &str) {
    <article>
        @* href is scheme-checked, text is auto-escaped *@
        <a href=@safe_url(site)>@author</a>
        <p>@body</p>

        @match body.len() {
            0 => { <em>No content</em> }
            n if n > 280 => { <span>Long post</span> }
            _ => {}
        }
    </article>
}

Batteries-included helpers

Currency, dates, pluralization, class-name merging, truncation and more — tree-shaken, so you only ship what you use.

@fn receipt(total_cents: i64, when: i64, qty: usize) {
    <ul>
        <li>@currency(total_cents as f64 / 100.0, "USD")</li>
        <li>@format_date(when, "%b %d, %Y")</li>
        <li>@pluralize(qty, "item")</li>   @* "3 items" *@
    </ul>
}

Embedded JS, CSS & JSON

First-class blocks for embedded languages with interpolation. Pairs beautifully with htmx and Alpine.js.

@fn counter(start: i32) {
    <div x-data=@```json { count: @start } ```@>
        <button x-on:click="count++">+</button>
        <output x-text="count"></output>
    </div>
}

Why not a runtime engine?

Tera, Handlebars and friends parse strings and resolve variables while your users wait — and surface typos as runtime errors. Waltzing does the work at build time.

Runtime engines

  • Template errors show up in production
  • Variables are stringly-typed
  • Parsing cost on every request
  • Escaping is opt-in / easy to forget
  • No compiler help for refactors

Waltzing

  • Template errors fail cargo build
  • Parameters are real Rust types
  • Compiled once — native at runtime
  • Escaped by default, everywhere
  • Rename a field, the compiler finds every use
Buffered and streaming render Async output to any writer Compile-time i18n Axum IntoResponse Source-mapped errors back to .wtz

Tooling that keeps up

Editor support

LSP for autocomplete and diagnostics, a tree-sitter grammar for highlighting, and extensions for Zed and VS Code. Plus a formatter and auto-fix.

Editor setup

AI-native

An MCP server gives Claude Code and other assistants tools to read, write and validate templates — with an LLM guide so they get the syntax right.

MCP & LSP

Drops into your build

A tiny build.rs compiles your templates/ directory. Errors from rustc are translated back to the original .wtz line.

Build integration

Ship type-safe HTML today

Install the compiler and scaffold a project in under a minute.

curl -fsSL https://waltzing.awesomike.com/install | bash