Nesting Templates

GoHT composes templates with @render, @children, and @slot.

  • @render calls another template and passes any required parameters.
  • Nested content under @render is passed to the rendered template.
  • @children renders nested content that was passed by the caller.
  • @slot renders named slot content, optionally with default content.

These directives work in Haml, Slim, and EGO templates. EGO uses command tags such as <%@render ... %> and <%@slot ... %>.

@render

Use @render when one template should call another template.

@haml Page(title string) {
	!!!
	%html
		%head
			%title= title
		%body
			%main
				=@children
}

@haml HomePage() {
	=@render Page("Home")
		%p Welcome home.
}

The same pattern in Slim:

@slim HomePageSlim() {
	= @render Page("Home")
		p Welcome home.
}

And in EGO:

@ego HomePageEgo() {
	<%@render Page("Home") { %>
		<p>Welcome home.</p>
	<% } %>
}

The parameters passed to @render are the parameters of the generated Go template function. If Page is generated as func Page(title string) goht.Template, then @render Page("Home") calls that generated function and renders the returned template.

Nested render content and @children

Nested content under @render remains in the calling template’s scope. The rendered template chooses where that content appears by using @children.

@haml ArticleLayout(title string) {
	%article
		%h1= title
		.content
			=@children
}

@haml ArticlePage(author string) {
	- summary := "Nested content can use local variables."
	=@render ArticleLayout("Composition")
		%p.byline Written by #{author}
		%p= summary
}

Rendered output from ArticlePage("Sam") is shaped like:

<article>
  <h1>Composition</h1>
  <div class="content">
    <p class="byline">Written by Sam</p>
    <p>Nested content can use local variables.</p>
  </div>
</article>

Slim uses the same = @render and = @children directives:

@slim ArticleLayoutSlim(title string) {
	article
		h1= title
		.content
			= @children
}

EGO uses command tags:

@ego ArticleLayoutEgo(title string) {
	<article>
		<h1><%= title %></h1>
		<div class="content">
			<%@children %>
		</div>
	</article>
}

Named slots

Named slots are useful for layouts that have several independent regions. A slot renders content that the caller passes with .Slot("name").

@haml DashboardLayout(title string) {
	!!!
	%html
		%head
			%title= title
		%body
			%header
				=@slot header
					%h1= title
			.layout
				%aside
					=@slot sidebar
				%main
					=@slot main
						%p Choose an item to get started.
			.notifications
				=@slot notifications
			%footer
				=@slot footer
					%small Copyright 2026
}

The header, main, and footer slots above include default content. GoHT renders default slot content only when the caller does not provide content for that slot. The sidebar and notifications slots render nothing unless the caller provides matching slotted templates.

Slim slot declarations follow the same shape:

@slim DashboardLayoutSlim(title string) {
	header
		= @slot header
			h1= title
	main
		= @slot main
			p Choose an item to get started.
}

EGO slots use <%@slot ... %> or a block form when they have defaults:

@ego DashboardLayoutEgo(title string) {
	<header>
		<%@slot header { %>
			<h1><%= title %></h1>
		<% } %>
	</header>
	<main>
		<%@slot main %>
	</main>
}

Passing slot content

Pass slot content from Go code by calling .Slot("name") on a template and passing it to Render.

err := DashboardLayout("Dashboard").Render(ctx, w,
	Header(user).Slot("header"),
	Sidebar(navItems).Slot("sidebar"),
	DashboardHome(stats).Slot("main"),
	Notifications(alerts).Slot("notifications"),
	Footer().Slot("footer"),
)

Slotted templates can contain their own slots. This lets a page fill a layout slot and still expose nested regions inside that slot.

err := DashboardLayout("Project").Render(ctx, w,
	Header(user).Slot("header"),
	ProjectSidebar(project).Slot("sidebar"),
	ProjectPage(project).Slot("main",
		ProjectSummary(project).Slot("summary"),
		ProjectActivity(events).Slot("activity"),
	),
	Notifications(alerts).Slot("notifications"),
	Footer().Slot("footer"),
)

In this example, ProjectPage(project) fills the layout’s main slot. The ProjectSummary and ProjectActivity templates fill slots that are declared inside ProjectPage.

@attributes is not a composition directive

@attributes expands dynamic attributes inside Haml and Slim attribute lists. It is not used to call templates or pass child content.

%a{href: "/dashboard", @attributes: #{attrs}} Dashboard

See Dynamic Attributes for details.