Templates

Use Go templates to render full pages and HTML fragments.

Templates are the center of the GO(A)HT UI model. HTML fragments are the primary UI API, and full pages are just larger compositions of those same fragments.

Template Roles

Keep templates in three groups:

  • layouts/ for the outer document shell
  • pages/ for full-page compositions
  • partials/ for reusable fragments and HTMX swap targets

That split makes the render contract obvious:

  • page handlers execute a page template
  • partial handlers execute a fragment template

Full Page vs Partial

A full page returns the whole document, usually through a layout:

{{ define "layouts/app.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ .Title }}</title>
  <script src="/static/vendor/htmx.min.js"></script>
  <link rel="stylesheet" href="/static/app.css">
</head>
<body hx-boost="true">
  {{ template "partials/nav.html" . }}
  <main>
    {{ block "content" . }}{{ end }}
  </main>
</body>
</html>
{{ end }}
{{ define "pages/game.html" }}
  {{ template "layouts/app.html" . }}
{{ end }}

{{ define "content" }}
  <section>
    <h1>{{ .Game.Name }}</h1>
    <div id="scoreboard">
      {{ template "partials/scoreboard.html" .Scoreboard }}
    </div>
  </section>
{{ end }}

A partial returns only the fragment being swapped:

{{ define "partials/scoreboard.html" }}
<section class="scoreboard">
  <p>{{ .Home.Name }}: {{ .Home.Score }}</p>
  <p>{{ .Away.Name }}: {{ .Away.Score }}</p>
</section>
{{ end }}

Use the page when the browser needs a document. Use the partial when HTMX needs a target to swap.

HTML Fragments Are the Primary UI API

In a GO(A)HT app, most UI changes should be handled by returning HTML fragments:

func (h *PartialHandlers) Scoreboard(w http.ResponseWriter, r *http.Request) {
    vm := h.games.ScoreboardVM(r.Context(), r.PathValue("id"))
    h.render.Partial(w, "partials/scoreboard.html", vm)
}
<div
  id="scoreboard"
  hx-ext="sse"
  sse-connect="/games/{{ .Game.ID }}/events"
  hx-get="/games/{{ .Game.ID }}/scoreboard"
  hx-trigger="load, sse:refresh-scoreboard"
  hx-swap="outerHTML"
>
  {{ template "partials/scoreboard.html" .Scoreboard }}
</div>

The browser asks for HTML. The server answers with HTML. That keeps the UI contract inspectable in the network panel and easy to test with plain handler tests.

If you use hx-ext="sse", include the HTMX SSE extension script in your layout before relying on sse-connect.

Compose Pages From Fragments

A page template should mostly arrange fragments rather than duplicate markup:

{{ define "content" }}
  {{ template "partials/game_header.html" .Header }}
  {{ template "partials/scoreboard.html" .Scoreboard }}
  {{ template "partials/player_list.html" .Players }}
{{ end }}

That gives you one source of truth for the UI surface that HTMX reuses later.

Rendering Helpers

Most apps benefit from two explicit render paths:

type Renderer struct {
    tmpl *template.Template
}

func (r *Renderer) Page(w http.ResponseWriter, name string, data any) error {
    return r.tmpl.ExecuteTemplate(w, name, data)
}

func (r *Renderer) Partial(w http.ResponseWriter, name string, data any) error {
    return r.tmpl.ExecuteTemplate(w, name, data)
}

The implementation can be identical. The distinction still matters because it tells readers what surface the handler returns.

Template Data

Shape template data for the surface being rendered:

type GamePageVM struct {
    Title      string
    Game       GameSummary
    Scoreboard ScoreboardVM
    Players    []PlayerVM
}

type ScoreboardVM struct {
    Home TeamScoreVM
    Away TeamScoreVM
}

Page view models can compose smaller fragment view models. Partial handlers can reuse the fragment-specific types directly.

Full-Page Refresh vs Partial Refresh

Both are valid. Choose based on the size of the UI change:

  • refresh the full page when the whole document meaningfully changes
  • refresh a partial when one section changes independently

BagTrax-style flows usually use:

  • full-page HTML for initial loads and large navigation changes
  • partial HTML for status panels, scoreboards, lists, and form feedback

Gotchas

  • Parse all templates together so define and template calls resolve correctly.
  • Pass . into nested templates unless you intentionally want an empty scope.
  • Keep fragment names stable. HTMX targets depend on them staying predictable.
  • Do not introduce JSON just to update server-rendered markup. Return the fragment instead.