Skip to main content
Fuego uses templ for type-safe HTML templates. Templ provides compile-time validation and excellent Go integration.

Why templ?

  • Type-safe - Compile-time checking catches errors early
  • Fast - No runtime template parsing
  • Go-native - Full Go code completion in templates
  • Small - Smaller binary size than text/template

File Conventions

FilePurposeRoute
page.templPage contentURL path
layout.templWraps pagesInherited by children

Creating Pages

Basic Page

Create app/dashboard/page.templ:
package dashboard

templ Page() {
    <div class="container">
        <h1>Dashboard</h1>
        <p>Welcome to your dashboard!</p>
    </div>
}
This maps to /dashboard.

Page with Props

package dashboard

type PageProps struct {
    User     User
    Stats    DashboardStats
}

templ Page(props PageProps) {
    <div class="container">
        <h1>Welcome, { props.User.Name }!</h1>
        <div class="stats">
            <p>Total tasks: { fmt.Sprint(props.Stats.TaskCount) }</p>
        </div>
    </div>
}

Creating Layouts

Root Layout

Create app/layout.templ:
package app

templ Layout(title string) {
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>{ title } - My App</title>
        <link rel="stylesheet" href="/docs/static/css/output.css"/>
        <script src="https://unpkg.com/[email protected]"></script>
    </head>
    <body class="bg-gray-100 min-h-screen">
        <nav class="bg-white shadow-sm">
            <div class="max-w-7xl mx-auto px-4">
                <a href="/docs/" class="text-xl font-bold">My App</a>
            </div>
        </nav>
        <main class="max-w-7xl mx-auto py-6">
            { children... }
        </main>
    </body>
    </html>
}
Layouts must include { children... } where page content should appear.

Nested Layouts

Create app/dashboard/layout.templ:
package dashboard

templ Layout() {
    <div class="flex">
        <aside class="w-64 bg-gray-800 text-white">
            <nav>
                <a href="/docs/dashboard">Overview</a>
                <a href="/docs/dashboard/settings">Settings</a>
            </nav>
        </aside>
        <div class="flex-1">
            { children... }
        </div>
    </div>
}
Nested layouts override parent layouts.

Dynamic Pages

Dynamic pages use bracket notation in directory names to capture URL parameters.

URL Parameters

Create app/posts/[slug]/page.templ:
package slug

templ Page(slug string) {
    <article>
        <h1>Post: { slug }</h1>
        <div hx-get={ "/api/posts/" + slug } hx-trigger="load">
            Loading content...
        </div>
    </article>
}
This maps to /posts/:slug. The slug parameter is automatically extracted from the URL and passed to your Page() function.

Parameter Matching

Fuego matches URL parameters to Page() parameters by name. Your parameter names should match the bracket directory names:
DirectoryPage SignatureMatch
[slug]Page(slug string)
[id]Page(id string)
[id]Page(userId string)⚠️ Warning
If parameter names don’t match, Fuego shows a warning during route generation. The page still renders, but mismatched parameters receive zero values.

Multiple Parameters

For nested dynamic routes:
// app/orgs/[orgId]/users/[userId]/page.templ
package userId

templ Page(orgId, userId string) {
    <div>
        <p>Organization: { orgId }</p>
        <p>User: { userId }</p>
    </div>
}
This maps to /orgs/:orgId/users/:userId.

Catch-All Routes

Use [...param] for catch-all routes:
// app/docs/[...slug]/page.templ
package slug

templ Page(slug string) {
    <div>
        <p>Path: { slug }</p>
    </div>
}
This matches /docs/anything/here/deeply/nested with slug = "anything/here/deeply/nested".

Pages with Additional Props

You can accept additional parameters beyond URL params. Non-URL parameters receive zero values unless you fetch data in a custom handler:
// app/users/[id]/page.templ
package id

type User struct {
    ID    string
    Name  string
    Email string
}

templ Page(id string, user User) {
    <div class="user-profile">
        if user.Name != "" {
            <h1>{ user.Name }</h1>
            <p>{ user.Email }</p>
        } else {
            <p>Loading user { id }...</p>
        }
    </div>
}
For pages that need to load data before rendering, consider using HTMX to load content dynamically, or create a custom route handler.
Due to Go’s restriction on brackets in import paths, Fuego creates temporary symlinks for bracket directories:
app/posts/[slug]/  →  app/posts/_slug/  (symlink)
These symlinks are:
  • Created automatically during fuego dev and fuego build
  • Should be added to .gitignore
  • Cleaned up when the dev server stops

Components

Creating Components

package components

templ Button(text string, variant string) {
    <button class={ "btn", "btn-" + variant }>
        { text }
    </button>
}

templ Card(title string) {
    <div class="card">
        <h2>{ title }</h2>
        <div class="card-body">
            { children... }
        </div>
    </div>
}

Using Components

package dashboard

import "myapp/app/_components"

templ Page() {
    <div>
        @components.Card("Statistics") {
            <p>Your stats here</p>
        }
        @components.Button("Save", "primary")
    </div>
}

HTMX Integration

Loading Data

templ TaskList() {
    <div id="task-list" hx-get="/api/tasks" hx-trigger="load">
        <p>Loading tasks...</p>
    </div>
}

Form Submission

templ AddTaskForm() {
    <form 
        hx-post="/api/tasks" 
        hx-target="#task-list"
        hx-swap="innerHTML"
        hx-on::after-request="this.reset()"
    >
        <input type="text" name="title" placeholder="New task..." required/>
        <button type="submit">Add Task</button>
    </form>
}

Toggle Actions

templ TaskItem(task Task) {
    <li class="flex items-center gap-3">
        <input 
            type="checkbox" 
            hx-post={ fmt.Sprintf("/api/tasks/toggle?id=%d", task.ID) }
            hx-target="#task-list"
            if task.Completed {
                checked
            }
        />
        <span class={ templ.KV("line-through", task.Completed) }>
            { task.Title }
        </span>
        <button 
            hx-delete={ fmt.Sprintf("/api/tasks?id=%d", task.ID) }
            hx-target="#task-list"
        >
            Delete
        </button>
    </li>
}

Full Example: Task Dashboard

// app/dashboard/page.templ
package dashboard

templ Page() {
    <div class="px-4 py-5 sm:p-6">
        <h1 class="text-2xl font-bold text-gray-900 mb-6">Dashboard</h1>
        
        <div class="bg-white shadow rounded-lg p-6 mb-6">
            <h2 class="text-lg font-medium text-gray-900 mb-4">Tasks</h2>
            
            <!-- HTMX-powered task list -->
            <div id="task-list" hx-get="/api/tasks" hx-trigger="load" hx-swap="innerHTML">
                <div class="text-gray-500">Loading tasks...</div>
            </div>
            
            <!-- Add task form with HTMX -->
            <form 
                class="mt-6 flex gap-4"
                hx-post="/api/tasks" 
                hx-target="#task-list"
                hx-swap="innerHTML"
                hx-on::after-request="this.reset()"
            >
                <input 
                    type="text" 
                    name="title" 
                    placeholder="New task..." 
                    class="flex-1 rounded-md border-gray-300"
                    required
                />
                <button 
                    type="submit"
                    class="px-4 py-2 bg-orange-600 text-white rounded-md"
                >
                    Add Task
                </button>
            </form>
        </div>
    </div>
}

Generating Pages

Use the CLI to generate pages:
# Basic page
fuego generate page dashboard

# Page with layout
fuego generate page admin/settings --with-layout

# Nested page
fuego generate page users/profile

Best Practices

Extract common UI patterns into components in a _components folder.
Pages should primarily compose components, not contain complex logic.
Let the server render HTML instead of writing client-side JavaScript.
templ catches errors during compilation. Fix warnings before deploying.

Next Steps