RUITL - Rust UI Template Language v0.2.2
Status: Pre-release (v0.2.2) — Core feature set stable. Breaking changes possible before v1.0 while SSG and ergonomics settle.
A template compiler for building type-safe HTML components in Rust, modelled on Go's templ. RUITL compiles .ruitl template files into sibling *_ruitl.rs files (committed to source control, reviewable in diffs) and links them into your binary at build time.
Key features
- Template compilation:
.ruitl→ sibling*_ruitl.rs(templ-style_templ.goconvention) - Type safety: Generated components with full Rust type checking
- Zero runtime parsing: Templates compiled away, pure Rust at render
- Cargo integration:
build.rsandruitlCLI share one compiler - Component props: Type-safe props with validation, defaults, generics
- Incremental builds: Hash-based skip when
.ruitlsource is unchanged - Watch mode:
ruitl compile --watchauto-recompiles on save - HTML generation: Clean, deterministic, attribute-order-stable output
- No JavaScript: Pure Rust, server-side rendering focus
Status
| Feature | State | Notes |
|---|---|---|
| Template parser | Stable | components, props, if/for/match, composition @X(...), imports |
| Generics | Stable (type params) | <T, U: Bound>. Lifetime params rejected with explicit error |
| Codegen | Stable | Deterministic attribute order; prop bindings emitted only when referenced |
| Incremental build | Stable | // ruitl-hash: header skip; CODEGEN_VERSION cache-buster |
| Watch mode | Stable (dev feature) | hotwatch-backed; 150ms debounce |
| Scaffolder | Stable | ruitl scaffold emits sibling-file projects with bin/ruitl.rs wrapper |
| Snapshot tests | Stable | insta + prettyplease; fixtures in tests/fixtures/snapshots/ |
| Minification | Optional | --features minify post-render via minify-html (planned) |
| Static site generation | Planned | ruitl build subcommand with [[routes]] config (planned) |
| Parser error context | Rustc-style frame | Line/col + caret + source context |
| Editor support | Stable | tree-sitter grammar + LSP w/ diagnostics, formatting, completion (@ + < + prop-names inside @X(...)), hover, go-to-definition |
| Formatter | Stable | ruitl fmt [--check] CLI + LSP textDocument/formatting. Idempotent. Preserves leading comments. |
| Raw-HTML expression | Stable | {!expr} inside a template body injects the runtime value as raw HTML (no escaping). |
| Template inheritance | Stable | @X(...) { body } + {children} slot. Auto-injects pub children: Html on the callee's Props when the slot is used. |
| Did-you-mean errors | Stable | Codegen validation suggests closest declared component/prop name on typos via Levenshtein. |
| Parallel compile | Stable | compile_dir_sibling fans out with rayon behind the parallel feature (default on). |
| Buffer-reuse render | Stable | Html::render_into(&mut String), render_with_capacity, len_hint for hot request loops. |
| SSR streaming | Stable | Html::to_chunks() splits a top-level Fragment for hyper::Body::wrap_stream. See examples/streaming_demo.rs. |
| Dev server | Stable (dev + server features) | ruitl dev watches .ruitl, serves SSE reload at /ruitl/reload so browsers auto-refresh. |
| Testing helpers | Optional (testing feature) |
ruitl::testing::{ComponentTestHarness, HtmlAssertion} + assert_html_contains! / assert_renders_to!. |
| AST debug dump | Stable | ruitl compile --emit-ast writes a pretty-Debug of the parser AST next to each source. |
See tests/fixtures/snapshots/*.snap for canonical codegen output.
Browse the gallery: examples/README.md indexes every
.ruitl fixture and example binary by learning goal.
Quick start
You can get started with RUITL in three ways:
Option 1: Explore the RUITL Repository (Development)
Clone and explore the RUITL repository with built-in examples:
# Clone RUITL repository
# Build RUITL
# Compile example templates in the repository
# Run the server integration example with live templates
# Server available at http://localhost:3000
This gives you immediate access to working examples and lets you experiment with RUITL templates directly in the repository.
Option 2: Use the Project Scaffolder (Recommended)
Create a complete project with examples and server support:
# Clone RUITL repository
# Create a new project with server and examples
# Navigate to your new project
# Compile templates and run (using included RUITL binary)
# Server available at http://localhost:3000
This creates a complete project structure with example components, HTTP server, static assets, and documentation.
Option 3: Manual Setup
1. Add RUITL to Your Project
# Cargo.toml
[]
= "0.2"
= { = "1.0", = ["full"] }
[]
= "2.3"
2. Create a Template
Create templates/Button.ruitl:
// Button.ruitl - A reusable button component
component Button {
props {
text: String,
variant: String = "primary",
disabled: bool = false,
}
}
ruitl Button(text: String, variant: String) {
<button class={format!("btn btn-{}", variant)} type="button">
{text}
</button>
}
3. Use Generated Components
The build process automatically generates Rust components:
use *;
// Generated components are available after build
// mod generated;
// use generated::*;
Project scaffolding
ruitl scaffold emits a ready-to-build project skeleton with optional server and example components.
Creating a New Project
# Create a basic RUITL project
# Create a project with HTTP server support
# Create a project with example components
# Create a full-featured project with both server and examples
# Specify target directory
Generated Project Structure
The scaffolder creates a complete project structure:
my-ruitl-project/
├── .gitignore # Git ignore file
├── Cargo.toml # Project configuration with dependencies
├── README.md # Project documentation
├── ruitl.toml # RUITL-specific configuration
├── bin/
│ └── ruitl.rs # Included RUITL CLI binary
├── src/
│ ├── main.rs # Main application (server if --with-server)
│ ├── lib.rs # Library code
│ └── handlers/ # HTTP handlers (if --with-server)
│ └── mod.rs
├── templates/ # RUITL template files
│ ├── Button.ruitl # Example button component
│ ├── Card.ruitl # Example card component
│ └── Layout.ruitl # Example layout component
├── static/ # Static assets
│ ├── css/
│ │ └── styles.css # Complete CSS framework
│ └── js/
│ └── main.js # Interactive JavaScript
├── templates/ # .ruitl sources AND their generated siblings (committed)
│ ├── Button.ruitl
│ ├── Button_ruitl.rs # Generated sibling (templ-style, checked in)
│ ├── Card.ruitl
│ ├── Card_ruitl.rs
│ ├── Layout.ruitl
│ ├── Layout_ruitl.rs
│ └── mod.rs # Auto-generated re-exports
└── examples/ # Additional examples (if --with-examples)
Note: Generated *_ruitl.rs files live next to their .ruitl sources (Go templ's _templ.go convention). They are reviewable and checked in. Running cargo run --bin ruitl -- compile or cargo build regenerates them.
Self-Contained Binary: Each scaffolded project includes its own RUITL CLI binary in the bin/ directory, so you don't need to install RUITL globally. All template compilation is done using cargo run --bin ruitl -- <command>.
Server Implementation Features
When using --with-server, the scaffolder generates:
- Complete HTTP Server: Built with Tokio and Hyper
- Routing System: Clean URL routing with static file serving
- Component Integration: Server-side rendering with RUITL components
- Static Assets: CSS and JavaScript served efficiently
- Error Handling: 404 pages and error responses
- Runs immediately with
cargo run
Example server routes:
http://localhost:3000/- Home page with welcome contenthttp://localhost:3000/about- About page with project infohttp://localhost:3000/static/*- Static file serving
Example Components
With --with-examples, you get three complete example components:
Button Component (templates/Button.ruitl):
component Button {
props {
text: String,
variant: String = "primary",
size: String = "medium",
disabled: bool = false,
onclick: String?,
}
}
ruitl Button(props: ButtonProps) {
<button
class={format!("btn btn-{} btn-{}", props.variant, props.size)}
disabled?={props.disabled}
onclick={props.onclick.as_deref().unwrap_or("")}
type="button"
>
{props.text}
</button>
}
Card Component with conditional rendering and Layout Component with full HTML structure are also included.
Getting Started with Scaffolded Project
After scaffolding:
# Navigate to your project
# Compile RUITL templates (using included binary)
# Build the project
# Run the server (if --with-server was used)
# Server starts at http://localhost:3000
# Or run as library (if no server)
Why the included binary? Each scaffolded project includes its own RUITL CLI wrapper (bin/ruitl.rs) that uses the same RUITL version as your project dependencies. This ensures version consistency and eliminates the need for global RUITL installation.
Scaffold Command Options
| Option | Description | Default |
|---|---|---|
--name <NAME> |
Project name | my-ruitl-project |
--target <PATH> |
Target directory | . (current directory) |
--with-server |
Include HTTP server implementation | false |
--with-examples |
Include example components | false |
--verbose |
Verbose output | false |
Dependencies Added
The scaffolder automatically configures appropriate dependencies:
Basic Project:
ruitl- Template engineserde- Serializationanyhow- Error handling
With Server (adds):
tokio- Async runtimehyper- HTTP serverserde_json- JSON handling
CLI commands
The ruitl binary drives template compilation, project scaffolding, formatting, and the dev server.
Installation
# Clone and build RUITL
# Use via cargo run for development
# Or install globally (after publishing)
# In scaffolded projects, use the included binary
Available Commands
scaffold - Create New Projects
Create a new RUITL project with optional server and examples:
# Basic usage
# Full options
Options:
--name <NAME>- Project name (default:my-ruitl-project)--target <PATH>- Target directory (default: current directory)--with-server- Include HTTP server implementation--with-examples- Include example components--verbose- Show detailed output
compile - Compile Templates
Compile .ruitl template files to Rust code. Each Foo.ruitl produces a sibling Foo_ruitl.rs in the same directory (templ-style, checked in).
# Basic compilation (reads templates/, writes sibling *_ruitl.rs files)
# Specify source directory
# Watch mode for development
# Full options
Options:
--src-dir <PATH>- Template source directory (default:templates)--watch- Watch for file changes and recompile automatically--verbose- Show detailed compilation output
dev - Development Server with Browser Reload
Watch .ruitl files, recompile on save, and push a reload event to any
browser subscribed to the sidecar SSE endpoint. Intentionally does NOT
manage your app server process — pair it with cargo watch -x run or run
your app manually in another terminal.
# Default — watch ./templates, sidecar on port 35729
# Custom directory and port
Add this script tag to your layout while in development:
Options:
--src-dir <PATH>- Template source directory (default:templates)--reload-port <PORT>- Reload sidecar port (default:35729)
The server exposes two endpoints:
GET /ruitl/reload.js— auto-reconnecting SSE client script.GET /ruitl/reload— SSE stream; firesevent: reloadafter each successful recompile.
version - Show Version
Display RUITL version information:
Global Options
Available for all commands:
--config <PATH>- Custom configuration file path--env <ENV>- Environment setting (default:development)--verbose- Enable verbose output--help- Show command help
Configuration File
Create ruitl.toml in your project root:
[]
= "my-project"
= "0.1.0"
= "My RUITL project"
= ["Your Name <your.email@example.com>"]
[]
= "templates"
= "src"
[]
= "127.0.0.1"
= 3000
= "static"
[]
= true
= false
Development Workflow
Working with Scaffolded Projects
For projects created with the scaffolder:
# 1. Create new project
# 2. Navigate to project
# 3. Start development with watch mode (using included binary)
&
# 4. Run the server (in another terminal)
# 5. Edit templates and see changes automatically
# Templates in templates/ are watched and recompiled
Working on RUITL Repository
For contributing to RUITL or using repository examples:
# 1. Clone and build RUITL
# 2. Compile repository templates
# 3. Run examples with live reload
# Visit http://localhost:3000
# 4. For development with watch mode
&
# 5. Edit templates/ files and see changes in examples
Examples
# Create a simple library project
# Create a full web application
# Compile templates with custom source path
# Development workflow with file watching
Template syntax
Component Definitions
Define reusable components with type-safe props:
component UserCard {
props {
name: String,
email: String,
role: String = "user",
avatar_url: String?,
is_verified: bool = false,
}
}
Template Implementation
Implement the component's HTML structure:
ruitl UserCard(name: String, email: String, role: String) {
<div class="user-card">
<div class="user-header">
<h3 class="user-name">{name}</h3>
<span class="user-role">{role}</span>
</div>
<div class="user-contact">
<p class="user-email">{email}</p>
</div>
</div>
}
Expression Interpolation
Use Rust expressions directly in templates:
ruitl Example(count: u32, items: Vec<String>) {
<div>
<h1>Items ({count})</h1>
<p>Status: {if count > 0 { "Has items" } else { "Empty" }}</p>
<p>First item: {items.first().unwrap_or(&"None".to_string())}</p>
</div>
}
Template Inheritance via {children}
Pass a body block into a component with @Name(props) { ... } and receive
it inside the callee with the {children} slot. Mirrors Go templ's
children-prop convention.
component Card {
props {
title: String,
}
}
ruitl Card(title: String) {
<div class="card">
<h2>{title}</h2>
<div class="body">{children}</div>
</div>
}
ruitl Page() {
@Card(title: "Welcome".to_string()) {
<p>First paragraph.</p>
<p>Second paragraph.</p>
}
}
Codegen auto-injects pub children: Html into the callee's Props struct
whenever its template body references {children}. Call sites without a
body block default the field to Html::Empty. Multiple {children} refs
in the same template are allowed; each expands to a clone of the slot.
The bare identifier {children} is the slot placeholder — {my.children}
or any dotted path stays a normal expression.
Build process
build.rs compiles any .ruitl files it finds under src/templates/ and templates/ on cargo build:
Project Structure
my-app/
├── Cargo.toml
├── build.rs # Auto-compile templates
├── src/
│ ├── main.rs
│ └── lib.rs
└── templates/ # Your .ruitl files
├── Button.ruitl
├── UserCard.ruitl
└── Layout.ruitl
Build Integration
Add to your build.rs:
// build.rs
Generated Code
Templates compile to efficient Rust code:
// Generated from Button.ruitl
;
Examples
Scaffolded Project Examples
The best way to explore RUITL is through the project scaffolder:
# Clone RUITL repository
# Create a complete example project
# Navigate and run the example
# Visit http://localhost:3000
This creates a complete project with:
- Three example components: Button, Card, and Layout
- HTTP server: Complete web application
- Static assets: CSS framework and JavaScript
- Multiple pages: Home, About, and 404 error pages
- Type-safe props: Demonstrates all RUITL features
Component Examples from Scaffolded Project
Button Component - Shows props with defaults:
// Usage in Rust code
let button = Button;
let props = ButtonProps ;
let html = button.render?;
Card Component - Shows conditional rendering:
// Usage with optional footer
let card = Card;
let props = CardProps ;
Layout Component - Shows full page structure:
// Complete page layout
let layout = Layout;
let props = LayoutProps ;
Repository Examples
The RUITL repository includes several built-in examples you can run immediately:
# Clone and build RUITL
# Run the server integration example (recommended)
# Visit http://localhost:3000 to see:
# - Live component rendering
# - Multiple page routing
# - Static asset serving
# - Type-safe component usage
# Other examples available:
Server Integration Example Features:
- Live Components: See Button, UserCard, and Page components in action
- Multiple Routes: Home, Users, About, and API endpoints
- Server-Side Rendering: Components rendered to HTML
- Type Safety: Demonstrates prop validation and error handling
- Navigation: Working page navigation with styled components
Original Demo
You can also run the original template compiler demo:
## Development workflow
Current status
Working features
- Build script template compilation
- CLI template compilation
- Basic template syntax (components, props, templates)
- Advanced template syntax (conditionals, loops, composition)
- Type-safe props with defaults and validation
- Expression interpolation with complex Rust expressions
- HTML element generation (all standard elements)
- Component trait implementation
- Cargo integration
- Conditional rendering (
if/elsestatements) - Loop rendering (
forloops over iterables) - Component composition (
@Componentsyntax) - Pattern matching (
matchexpressions) - Import statements
- Boolean and primitive type operations
- Complex nested template structures
Enhancement opportunities
- Hot reload development mode (
ruitl dev— SSE browser reload; pair withcargo watch -x runto restart the app binary) - IDE support and syntax highlighting (Zed + VS Code extensions, tree-sitter grammar)
- Advanced error messages with suggestions (did-you-mean for unknown components/props)
- Template inheritance (
{children}slot +@Card(...) { body }syntax) - Performance optimizations (rayon parallel compile, buffer-reuse render API, criterion benches)
Roadmap
-
Advanced template featuresCOMPLETE -
Hot reload development modeCOMPLETE (ruitl dev) -
IDE support and syntax highlightingCOMPLETE -
Performance optimizations and cachingCOMPLETE -
Template inheritanceCOMPLETE ({children}slot) -
Server-side streamingCOMPLETE (Html::to_chunks+streaming_demo) -
Component testing utilitiesCOMPLETE (ruitl::testing,testingfeature) -
Template debugging toolsPARTIAL (ruitl compile --emit-ast)
Configuration
Configure template compilation in your Cargo.toml:
[]
= "templates"
= "generated"
Editor support
Four editor-integration crates ship alongside the compiler:
tree-sitter-ruitl— tree-sitter grammar for syntax highlighting in Neovim, Helix, Zed, and any tree-sitter-aware editor. Injects therustlanguage into{ ... }expression spans so embedded Rust highlights too.ruitl_lsp— Language Server. Reports parse and codegen errors astextDocument/publishDiagnosticsin real time. Supports formatting, completion (@+<+ prop-names inside@X(...)), hover, and go-to-definition. Install viacargo install --path ruitl_lsp.zed-extension-ruitl— Zed extension that bundles the tree-sitter grammar and wires the LSP over stdio. Local install:zed: install dev extension→ point atzed-extension-ruitl/.vscode-extension-ruitl— VS Code extension bridgingruitl-lspplus a TextMate grammar fallback for syntax highlighting. Package locally withnpx vsce package && code --install-extension ruitl-0.1.0.vsix.
Roadmap beyond this release:
- Marketplace publishing — Zed registry + VS Code Marketplace + npm for
tree-sitter-ruitl(blocked on publisher account setup, not engineering). - Rust-aware expression completion — completion inside
{...}depends on a rust-analyzer bridge; intentionally out of scope.
Fallback if you don't wire the LSP: enable watch mode (ruitl compile --watch) in one terminal and let the parser+codegen errors from the watcher guide you.
FAQ
Q: How does RUITL compare to other templating solutions?
A: RUITL compiles templates to Rust code at build time, so there is no runtime parser and prop types are checked by rustc.
Q: Can I use existing Rust code in templates?
A: Yes. Expressions inside {...} are arbitrary Rust — function calls, method chains, closures.
Q: Is RUITL production ready?
A: v0.2 is pre-release. Core and advanced features (conditionals, loops, composition, {children}, generics) are stable; breaking changes are still possible before v1.0 while the static-site story and ergonomics settle.
Q: How does performance compare to runtime templating?
A: Templates compile to native Rust with no per-request parser. Render is a tree walk writing escaped text into a Write target.
Contributing
Contributions are welcome. Current priorities:
- Static-site generation (
ruitl buildsubcommand, planned). - Marketplace publishing for the Zed and VS Code extensions.
- npm release for the
tree-sitter-ruitlgrammar. - Rust-aware completion inside
{...}expressions (needs a rust-analyzer bridge). - Additional real-world examples beyond
server_integrationandstreaming_demo.
See the status table near the top for what already ships.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Acknowledgments
- Inspired by templ for Go.
- Thanks to early contributors and testers.
Contribute via issues.