Skip to main content
This guide covers everything you need to know about writing templates in Mizu’s view system. We’ll explore Go’s template syntax, how data flows through your templates, and common patterns.

Template Basics

Mizu uses Go’s standard html/template package. Templates are HTML files with special markers ({{ and }}) for dynamic content.
<!-- views/pages/home.html -->
<h1>Hello, {{.Data.Name}}!</h1>
<p>Welcome to our site.</p>

Accessing Data

In Mizu templates, your data is wrapped in a structure. Access your data using .Data:
// In your handler:
view.Render(c, "home", view.Data{
    "Title":   "Welcome",
    "User":    user,
    "Items":   items,
})
<!-- In your template: -->
<h1>{{.Data.Title}}</h1>
<p>Hello, {{.Data.User.Name}}</p>

The Template Data Structure

Templates receive this structure:
FieldDescriptionAvailable In
.Page.NameTemplate name (e.g., “home”)Pages and Layouts
.Page.LayoutLayout name (e.g., “default”)Pages and Layouts
.DataYour data from the handlerPages and Layouts
.ContentRendered page contentLayouts only

Go Template Syntax

Outputting Values

Use .Data. to access your data:
<!-- Simple value -->
<h1>{{.Data.Title}}</h1>

<!-- Nested struct field -->
<p>Author: {{.Data.Post.Author.Name}}</p>

<!-- Map key -->
<p>Setting: {{.Data.Settings.theme}}</p>

Conditions

Use if to show content conditionally:
{{if .Data.LoggedIn}}
    <p>Welcome back!</p>
{{else}}
    <p>Please log in.</p>
{{end}}

{{if .Data.Error}}
    <div class="error">{{.Data.Error}}</div>
{{end}}

Comparison Operators

Go templates provide comparison functions:
FunctionDescriptionExample
eqEqual{{if eq .Data.Status "active"}}
neNot equal{{if ne .Data.Count 0}}
ltLess than{{if lt .Data.Price 100}}
leLess than or equal{{if le .Data.Stock 5}}
gtGreater than{{if gt .Data.Price 100}}
geGreater than or equal{{if ge .Data.Age 18}}
{{if eq .Data.Status "active"}}Active{{end}}
{{if ne .Data.Count 0}}Has items{{end}}
{{if gt .Data.Price 100}}Premium{{end}}
{{if le .Data.Stock 5}}Low stock{{end}}

Boolean Logic

Combine conditions with and, or, and not:
{{if and .Data.LoggedIn .Data.IsAdmin}}
    Admin controls
{{end}}

{{if or .Data.Error .Data.Warning}}
    <div class="alert">Check messages</div>
{{end}}

{{if not .Data.Disabled}}
    <button>Click me</button>
{{end}}

Loops

Use range to iterate over slices, arrays, or maps:
<!-- Loop over slice -->
<ul>
    {{range .Data.Items}}
    <li>{{.}}</li>
    {{end}}
</ul>

<!-- With index -->
<ul>
    {{range $index, $item := .Data.Items}}
    <li>{{$index}}: {{$item}}</li>
    {{end}}
</ul>

<!-- Empty case -->
{{range .Data.Items}}
    <li>{{.}}</li>
{{else}}
    <li>No items found.</li>
{{end}}

Looping Over Structs

When ranging over a slice of structs:
{{range .Data.Users}}
    <!-- Inside range, . is the current user -->
    <div class="user">
        <h3>{{.Name}}</h3>
        <p>{{.Email}}</p>
    </div>
{{end}}

Looping Over Maps

{{range $key, $value := .Data.Settings}}
    <p>{{$key}}: {{$value}}</p>
{{end}}

Variables

Assign values to variables with :=:
{{$name := .Data.User.Name}}
<p>Hello, {{$name}}!</p>

{{$fullName := printf "%s %s" .Data.FirstName .Data.LastName}}
<p>{{$fullName}}</p>

The Root Context ($)

Inside a range loop, . refers to the current item. Use $ to access the root context:
{{range .Data.Users}}
    <p>{{.Name}}</p>
    {{if $.Data.ShowEmail}}
        <p>{{.Email}}</p>
    {{end}}
{{end}}
$ always refers to the original data passed to the template.

Pipelines

Chain operations with |:
<!-- Apply function to value -->
<p>{{.Data.Name | upper}}</p>

<!-- Chain multiple functions -->
<p>{{.Data.Title | lower | trim}}</p>

Comments

Template comments are stripped from output:
{{/* This comment won't appear in the HTML */}}
<p>Visible content</p>

Whitespace Control

Use - to trim whitespace:
{{- .Data.Name -}}    <!-- Trims whitespace before and after -->
{{- .Data.Name}}      <!-- Trims whitespace before -->
{{.Data.Name -}}      <!-- Trims whitespace after -->
This is useful for keeping your HTML clean:
<ul>
    {{- range .Data.Items}}
    <li>{{.}}</li>
    {{- end}}
</ul>

Layouts and Pages

Layout Structure

Layouts use {{.Content}} to include the rendered page:
<!-- views/layouts/default.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.Data.Title}}</title>
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
    </nav>
    <main>
        {{.Content}}
    </main>
    <footer>
        Copyright 2024
    </footer>
</body>
</html>

Page Structure

Pages are simple - just write HTML with template expressions:
<!-- views/pages/home.html -->
<h1>Welcome, {{.Data.Name}}!</h1>
<p>This is the home page.</p>
The page content is rendered first, then inserted into the layout’s {{.Content}}.

Common Patterns

Conditional Classes

<div class="card {{if .Data.Featured}}card-featured{{end}}">
    ...
</div>

<!-- Multiple conditional classes -->
<button class="btn {{if .Data.Primary}}btn-primary{{else}}btn-secondary{{end}} {{if .Data.Large}}btn-lg{{end}}">
    {{.Data.Label}}
</button>

Dynamic Attributes

<input
    type="text"
    name="{{.Data.Name}}"
    value="{{.Data.Value}}"
    {{if .Data.Required}}required{{end}}
    {{if .Data.Disabled}}disabled{{end}}
>

Safe HTML Output

By default, Go templates escape HTML to prevent XSS attacks:
<!-- Escaped (safe) - shows HTML as text -->
<div>{{.Data.Content}}</div>
<!-- If .Data.Content is "<b>bold</b>", outputs: &lt;b&gt;bold&lt;/b&gt; -->
To output raw HTML (use only with trusted content!):
import "html/template"

view.Data{
    "Content": template.HTML("<b>bold</b>"),
}
<div>{{.Data.Content}}</div>
<!-- Now outputs: <b>bold</b> -->

Empty/Nil Checks

<!-- Check for empty string -->
{{if .Data.Description}}
    <p>{{.Data.Description}}</p>
{{end}}

<!-- Check slice length -->
{{if .Data.Items}}
    <ul>
        {{range .Data.Items}}
        <li>{{.}}</li>
        {{end}}
    </ul>
{{else}}
    <p>No items found.</p>
{{end}}
<a href="/users/{{.Data.User.ID}}">View Profile</a>
<a href="/posts?page={{.Data.Page}}">Next Page</a>

Formatting Numbers and Dates

Use custom functions or printf:
<p>Price: ${{printf "%.2f" .Data.Price}}</p>
<p>Count: {{printf "%d" .Data.Count}}</p>
For dates, either format in Go or use a custom function:
view.Data{
    "FormattedDate": time.Now().Format("January 2, 2006"),
}
Or add a custom function:
view.Config{
    Funcs: template.FuncMap{
        "formatDate": func(t time.Time) string {
            return t.Format("Jan 2, 2006")
        },
    },
}
<p>Published: {{formatDate .Data.PublishedAt}}</p>

Debugging Templates

Use printf with %#v to see the Go syntax representation:
<pre>{{printf "%#v" .Data}}</pre>

Check Types

<p>Type: {{printf "%T" .Data.Items}}</p>

Enable Development Mode

For detailed error messages with line numbers:
view.New(view.Config{
    Development: true,
})

Best Practices

1. Keep Templates Simple

Put complex logic in Go handlers, not templates:
// Good: logic in handler
isVIP := user.TotalPurchases > 1000 && user.MemberSince.Before(cutoff)
view.Data{"IsVIP": isVIP}
<!-- Template just uses the result -->
{{if .Data.IsVIP}}VIP Member{{end}}

2. Use Meaningful Variable Names

{{range $user := .Data.Users}}
    <p>{{$user.Name}}</p>
{{end}}

3. Document Expected Data

Add comments at the top of templates:
{{/*
    Expected data:
    - Title: string
    - User: {Name, Email}
    - Posts: []Post
*/}}

4. Escape User Input

Always let Go’s default escaping protect against XSS. Only use template.HTML for content you trust completely.

5. Use Whitespace Trimming

Keep generated HTML clean:
<ul>
{{- range .Data.Items}}
    <li>{{.}}</li>
{{- end}}
</ul>