Roc Template Language (RTL) is a developer tool for the Roc programming language that turns .rtl template files into a generated Pages.roc module, where each template becomes a type-checked Roc function. It takes a single record argument called model and returns rendered string output, giving you compile-time validation for HTML or any other textual content.
What is Roc Template Language (RTL)?
RTL is a template engine designed specifically for Roc, a functional programming language. The core idea is that you write templates containing literal text plus RTL tags for dynamic values, loops, conditionals, and pattern matching. Running the rtl command in a directory containing .rtl files generates Pages.roc, which exposes one Roc function per template. Each function accepts a model argument of any type (normally a record) and returns the fully rendered string. Because the generated code is ordinary Roc, the Roc compiler validates both template syntax and the types of all interpolated values. The project is authored by Isaac Vando and hosted on GitHub under the name rtl.
Key Features
- Compile-time type checking — Template logic is translated to Roc expressions, so type mismatches in your model or function calls are caught when your project compiles, not at runtime.
- Automatic HTML escaping — Values inserted with
{{ model.name }}are escaped by default, preventing cross-site scripting (XSS) vulnerabilities in HTML output. - Raw interpolation — Triple-bracket syntax
{{{ model.dynamic_html }}}inserts unescaped content, which is useful for non-HTML content types or combining multiple rendered templates. - List expansion — The
{|list pattern : list_expr |}...{|endlist|}block repeats content for each element, and the pattern can be any Roc pattern such as(x, y). - Conditionals —
{|if condition |}...{|endif|}and optional{|else|}blocks support any Roc boolean expression, so comparisons and function calls work directly. - When expressions — Pattern match on tag unions with
{|when x |}...{|is Ok(y) |}...{|endwhen|}syntax, integrating with Roc's tag union types. - Module imports —
{|import MyModule |}brings an arbitrary Roc module into scope for use inside template expressions. - Simple CLI workflow — Run
rtlwith no arguments in a directory to process all.rtlfiles and outputPages.rocautomatically.
Who should use RTL?
- Roc developers building server-rendered web pages or any generated text who want template errors surfaced as compiler errors instead of runtime failures.
- Teams adopting Roc who need a templating solution that leverages Roc's structural typing, type inference, and tag unions without introducing a separate template DSL.
- Tooling authors looking for a compile-time-checked template language that can generate not only HTML but also JSON, XML, Markdown, or configuration files.
What can you do with RTL?
- Generate type-safe HTML pages — Write a template for each page, pass a record containing page data, and get compile-time verification that all referenced fields exist and have the correct types.
- Create reusable partial templates — Render sub-templates and insert their output via raw interpolation into other templates, composing larger documents from smaller pieces.
- Produce non-HTML artifacts — Use raw interpolation to emit JSON, XML, or text without HTML escaping, making RTL a general-purpose text generator.
- Build with pattern matching — Use
whenexpressions to branch on tag unions likeOk/Err, producing type-safe error handling directly in the template layer.
How does RTL work?
Write a .rtl file mixing literal content with RTL tags, then run the rtl executable in that directory. The tool parses each template and generates Pages.roc, a normal Roc source file that defines one function per template. For example, a template named hello.rtl becomes a function Pages.hello that takes a model record and returns the rendered HTML string. The generated functions are fully type-checked by the Roc compiler, so any wrong field name, non-string interpolation, or malformed tag becomes a compile error.
Pros and cons
- Pros: Built-in XSS protection via HTML escaping; first-class integration with Roc's type system; supports common template constructs (loops, conditionals, pattern matching, imports); no runtime dependencies beyond the generated Roc code.
- Cons: Must be built from source because no precompiled binaries are provided; optimized builds can take longer than 30 seconds; syntax highlighting for
.rtlfiles requires manually mapping*.rtlto HTML in editor settings; the project is early-stage, with whitespace control, tag escaping, and hot reloading still on the todo list.
FAQ
What file does RTL generate?
Running rtl in a directory containing .rtl templates produces a single file named Pages.roc. It exposes one Roc function for each template, and each function takes a model argument and returns the rendered string.
Is HTML escaping automatic for normal interpolation?
Yes. Values inserted with double curly brackets, like {{ model.name }}, are HTML-escaped before insertion. Use triple brackets ({{{ ... }}}) to insert raw, unescaped content.
Can I use RTL with non-HTML output?
Yes. RTL is designed for any textual content. Since normal interpolation escapes HTML, you can use raw interpolation to output JSON, XML, Markdown, or other formats without unwanted escaping.
Does RTL need to be compiled from source?
Currently yes. The README provides build instructions using roc build rtl.roc --optimize and moving the resulting binary to /usr/local/bin. No prebuilt releases or package-manager installs are documented.
What are the project's future plans?
The repository's todo list includes adding control over whitespace around RTL tags, allowing tags to be escaped, exploring real hot code reloading, switching generated code to buffer-passing style for fewer copies, benchmarking runtime performance, and improving error messages for incomplete tags.







