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
@fn greeting(name: &str, admin: bool) {
<h1>Hello, @name 👋</h1>
@if admin {
<span class="badge">Admin</span>
}
}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.
@fn price_tag(label: &str, cents: i64) {
<div class="tag">
<span>@label</span>
<b>@currency(cents as f64 / 100.0, "USD")</b>
</div>
}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
IntoResponse Source-mapped errors back to .wtzTooling 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 setupAI-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 & LSPDrops into your build
A tiny build.rs compiles your templates/ directory. Errors from rustc are translated back to the original .wtz line.
Ship type-safe HTML today
Install the compiler and scaffold a project in under a minute.
curl -fsSL https://waltzing.awesomike.com/install | bash