HTML
March’s ~H sigil generates safe HTML. String interpolations inside ~H are auto-escaped — user input can’t inject markup. Html.raw opts out for trusted content you’ve already sanitized.
The ~H sigil
let name = "<script>alert(1)</script>"
let html = ~H"<p>Hello, ${name}!</p>"
-- renders: <p>Hello, <script>alert(1)</script></p>
Multi-line templates with triple quotes:
fn render_card(title : String, body : String) : IOList do
~H"""
<div class="card">
<h2>${title}</h2>
<p>${body}</p>
</div>
"""
end
Trusted HTML
Html.raw marks a string as already safe — ~H won’t escape it:
let icon = Html.raw("<svg>...</svg>")
let html = ~H"<button>${icon} Click me</button>"
Use Html.raw only for content you’ve verified or generated yourself, never for user input.
Layouts and partials
fn layout(title : String, body : IOList) : IOList do
~H"""
<!DOCTYPE html>
<html>
<head><title>${title}</title></head>
<body>${Html.raw(IOList.to_string(body))}</body>
</html>
"""
end
fn page() : IOList do
layout("Home", ~H"<h1>Welcome</h1>")
end
CSRF protection
~H automatically injects CSRF tokens into forms with method="post":
fn login_form() : IOList do
~H"""
<form method="post" action="/login">
<input name="email" type="email" placeholder="Email">
<input name="password" type="password" placeholder="Password">
<button>Log in</button>
</form>
"""
end
The hidden _csrf_token field is injected automatically. Validate it server-side with CSRF.protect(conn) middleware.
Complete example: a user list page
mod UserList do
type User = { name : String, email : String, admin : Bool }
fn render_badge(u : User) : IOList do
if u.admin do
~H"<span class='badge badge-admin'>admin</span>"
else
~H""
end
end
fn render_user(u : User) : IOList do
let badge = render_badge(u)
~H"""
<li class="user-row">
<strong>${u.name}</strong>
<span class="email">${u.email}</span>
${badge}
</li>
"""
end
fn render(users : List(User)) : IOList do
let items = Html.list(users, render_user)
let count = int_to_string(List.length(users))
~H"""
<h1>Users (${count})</h1>
<ul class="user-list">${items}</ul>
"""
end
fn main() do
let users = [
{ name: "Alice", email: "alice@example.com", admin: true },
{ name: "Bob", email: "bob@example.com", admin: false }
]
print(IOList.to_string(render(users)))
end
end