14.3 Passing Context to Partials: The Dot and Custom Dicts
Right, so you’ve broken your UI into partials. Good for you. Now you’re staring at a template that looks like this, wondering how the heck you get data into that isolated little island.
{{ template "user-card" }}
It renders, but it’s a ghost town. Your brilliant user-card partial is starving for data. This is where you stop just including a partial and start calling it with arguments. Welcome to the main event.
The Dot: Passing the Entire Context
The simplest way to feed your partial is to just hand it your entire current context (a.k.a., “the dot”). You do this by piping the dot into the template call.
{{ range .Users }}
<div class="user-list">
<!-- Pipe the current loop item (a user) into the partial -->
{{ template "user-card" . }}
</div>
{{ end }}
Here’s what’s happening: Inside the range loop, the dot (.) has been redefined as each individual User struct. When you say {{ template "user-card" . }}, you’re passing that single user object to the partial. Inside user-card, the dot is now that user, not the original page-level data.
This is perfect for partials that need to operate on a single object—a card, a row, a details panel. The partial’s world becomes blissfully simple; it just knows about one user.
{{ define "user-card" }}
<!-- Here, . is the user object we passed -->
<div class="card">
<h3>{{ .Name }}</h3>
<p>Email: {{ .Email }}</p>
</div>
{{ end }}
The Pitfall: This is all-or-nothing. Your partial now has zero access to any other data from the parent template—like site-wide settings, a CSRF token, or a IsUserAdmin function from your FuncMap. It’s locked in a room with just the user. This is often exactly what you want for encapsulation, but it’s a common headache when you suddenly need one more thing.
Crafting a Custom Dictionary
So what do you do when your user-card needs to render the user and know if the current viewer is an admin? You can’t just pass the user; that loses the admin flag. And you can’t pass the entire parent context; that would overwrite the user.
You build a custom dictionary. This is the “oh, we’re serious now” move.
Go’s template package allows you to pass a map[string]interface{} (or any struct, but a map is more flexible) to a template. This lets you create a bespoke data context, piecing together exactly what the partial needs to do its job.
{{ range .Users }}
<div class="user-list">
{{ template "user-card" dict "User" . "IsAdmin" $.CurrentUser.IsAdmin "Settings" $.SiteSettings }}
</div>
{{ end }}
Let’s break this down. The dict function (which is provided by Go’s template library) constructs a map for you right there in the template.
"User" .-> The key"User"is set to the value of the current loop item (the user)."IsAdmin" $.CurrentUser.IsAdmin-> The key"IsAdmin"is set to a value from the root context (hence the$, which always references the top-level data)."Settings" $.SiteSettings-> Another value from the root context.
Now, inside the partial, the dot is no longer a User; it’s this custom map you built.
{{ define "user-card" }}
<div class="card">
<h3>{{ .User.Name }}</h3>
<p>Email: {{ .User.Email }}</p>
<!-- Check the IsAdmin flag from our custom dict -->
{{ if .IsAdmin }}
<p class="admin-badge">ADMIN</p>
<button>Edit User</button> <!-- This button needs .User.ID, which we have! -->
{{ end }}
<!-- Use the site settings from the root context -->
<p>Theme: {{ .Settings.Theme }}</p>
</div>
{{ end }}
See? The partial has everything it needs: the specific user object, the admin flag, and the global settings, all accessible via the keys you defined in the dict. It’s perfectly self-contained and doesn’t rely on the vagaries of the parent’s scope.
The Gotcha: The syntax inside the partial changes. You’re now accessing fields through the map’s keys (.User.Name) instead of directly (.Name). This is a trade-off for immense power and clarity. It makes the partial’s dependencies explicitly defined right at the call site, which is a win for long-term maintenance. You’re not wondering “where did that variable come from?”; you can see it being passed in on the dict line.