TECHNOLOGY
Developer releases zt, a type-safe HTML templating library for Zig
A longtime web developer has released zt, an adaptation of Go's Templ for Zig. Templates are turned into Zig code at build time, so template mistakes show up as compiler errors.
Image: Qarbon IT
A web developer has released zt, a templating library for the Zig programming language. It is an adaptation of Templ, a templating tool for Go. The project is not very mature yet, and the author is asking for feedback.
The author says they have done web development since around 2000, using raw PHP, string concatenation in Python, Smarty in PHP, Template Toolkit in Perl, and Jinja/Django templates in Python. They stopped working on frontends as the industry moved to single-page apps (SPAs) and JavaScript frameworks. Two years ago they picked up Zig, a low-level language, and began building their own libraries, zio and Dusty.
Those libraries were first meant for API backends that return JSON or MessagePack. The author then found that web development in Zig is manageable too. Arena allocators, which free many allocations at once, mean you don't have to free each string by hand. Zig's comptime feature, which runs code at compile time, allows high-level interfaces without losing speed. With htmx or Datastar, they say you can build SPAs without writing JavaScript or TypeScript.
Templating was the missing piece. The author did not want to go back to string concatenation, which they compare to the pre-2000 CGI days. They set four requirements. Templates had to be type-safe, with inputs checked at compile time. They had to be fast, with no allocations and output written straight to std.Io.Writer. They needed safe defaults, with all inputs escaped to guard against XSS (cross-site scripting attacks). And they should still feel like writing HTML.
The author says no existing tool met all four. They were impressed by Ziex, which brings a JSX-style approach to Zig, and credit its creator, Nurul Huda, for the syntax and the way Zig code is mixed into templates. Ziex is a WASM framework, which they did not want. After more research they found Templ for Go, and decided to build something similar for Zig.
Here is how zt works. Templates are parsed and turned into Zig code during zig build. Templates can import app code, and app code can import templates, so all parameters are fully typed. The generated code only writes to a std.Io.Writer. You can send that output to a socket or collect it in a buffer. In Dusty, rendering a page is a single call: res.render(.html, pages.HomePage, .{user}).
In the author's example, a Layout template takes a title and wraps child content using @children. A HomePage template takes an optional user and shows either a welcome-back message with a profile link or a guest greeting with a login link. Control flow is plain Zig, so if, for and switch work as expected.
All dynamic values are HTML-escaped by default, in both text and attributes. Values in URL attributes such as href or src are also checked for dangerous schemes. To output raw HTML, you must write {!value}, which makes it easy to spot. The parser also requires every element to be properly closed. The author says this does not protect against everything, but it removes the most common sources of XSS.
The author's favorite part is that template errors are Zig compiler errors. In their example, a typo like {u.nmae} instead of {u.name} fails the build with a message that no field named 'nmae' exists in the User struct. The error points at the generated code, and each line carries a comment with the original template location. Passing a string where HomePage expects a ?User also fails to compile.
The author also notes a bug in a main-function snippet they show. It involves error handling in cases where std.Io cancellation matters. They plan to write a blog post and point readers to a Ziggit thread. They say Dusty's render function, which wraps the writer handling, avoids the problem.
One syntax question is still open. Following Templ, a line starting with if, for or switch is parsed as code. That means plain text such as "if you forgot your password…" on its own line is a parse error. You have to wrap it in an element or write it as a string expression. The author is considering @if, @for and @switch, as in Razor or Twirl, which would remove the ambiguity but make templates a bit noisier. They ask for opinions in a GitHub issue.
A TODO example in Dusty, built with htmx, shows the library in use. The author says several people already use zt. Feedback can go to GitHub issues or the Zig Discord.