Skip to content
gohacks.dev
Web

A Templ Cheat Sheet for Syntax Components and Helpers

A one page templ cheat sheet: component syntax, control flow, attributes, css and script components, children, fragments, helpers and commands in one program.

6 min read
Summarize in AI
Flat illustration of an editor window full of code.

This is the reference page for the templ series: syntax, helpers and commands in tables, with one component that uses all of it so you can see the output. The explanations are in the tutorial, the generated code in the first-component article, and the safety rules in the escaping article. It belongs to the web cluster that ends in a production HTTP server. Everything ran on Go 1.25.6 with templ v0.3.1001.

Tested with Go 1.25.6 and templ v0.3.1001, macOS on Apple Silicon (arm64)

One component that uses everything

sheet.templText
package main

import (
	"fmt"
	"strings"
)

type User struct {
	Name  string
	Admin bool
	Tags  []string
}

css highlight() {
	background-color: #fffbdd;
	border-left: 4px solid #d4a72c;
}

script confirmDelete(name string) {
	return confirm("Delete " + name + "?");
}

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

templ Badge(label string, attrs templ.Attributes) {
	<span class="badge" { attrs... }>{ label }</span>
}

templ Everything(u User, items []string, kind string) {
	// a Go comment: not in the output
	<!-- an HTML comment: in the output -->
	{{ upper := strings.ToUpper(u.Name) }}
	@Card("Profile") {
		<p>{ upper } has { fmt.Sprint(len(items)) } items</p>
		if u.Admin {
			@Badge("admin", templ.Attributes{"data-level": "2"})
		} else {
			@Badge("member", nil)
		}
		switch kind {
			case "a":
				<p>kind A</p>
			case "b":
				<p>kind B</p>
			default:
				<p>other kind</p>
		}
		<ul class={ "list", templ.KV("empty", len(items) == 0) }>
			for i, it := range items {
				<li data-i={ fmt.Sprint(i) } id={ "item-" + it }>{ it }</li>
			}
		</ul>
		<a href={ templ.URL("/users/" + u.Name) } class={ highlight() }>profile</a>
		<button type="button" onclick={ confirmDelete(u.Name) }>Delete</button>
		<input value={ u.Name } disabled?={ !u.Admin } required?={ true }/>
		@templ.Fragment("tags") {
			<p>
				for _, t := range u.Tags {
					<em>{ t }</em>
				}
			</p>
		}
		@templ.Raw("<hr/>")
	}
}
main.goGo
package main

import (
	"context"
	"fmt"
	"strings"
)

func main() {
	var sb strings.Builder
	err := Everything(User{Name: "ada", Admin: true, Tags: []string{"go", "web"}}, []string{"x", "y"}, "b").Render(context.Background(), &sb)
	if err != nil {
		panic(err)
	}
	fmt.Println(strings.ReplaceAll(sb.String(), "><", ">\n<"))
}
Shell
go get github.com/a-h/[email protected] >/dev/null 2>&1
go get -tool github.com/a-h/templ/cmd/[email protected] >/dev/null 2>&1
go tool templ generate >/dev/null 2>&1
go run .
Text
<!-- an HTML comment: in the output -->
<section class="card">
<h2>Profile</h2>
<p>ADA has 2 items</p>
<span class="badge" data-level="2">admin</span> <p>kind B</p> <ul class="list">
<li data-i="0" id="item-x">x</li>
<li data-i="1" id="item-y">y</li>
</ul>
<style type="text/css">.highlight_b768e7b3{background-color:#fffbdd;border-left:4px solid #d4a72c;}</style>
<a href="/users/ada" class="highlight_b768e7b3">profile</a> <script>function __templ_confirmDelete_9e4a(name){return confirm("Delete " + name + "?");
}</script>
<button type="button" onclick="__templ_confirmDelete_9e4a(&#34;ada&#34;)">Delete</button> <input value="ada" required>
<p>
<em>go</em>
<em>web</em>
</p> <hr/>
</section>

The output shows what each construct produces: the Go comment is gone and the HTML comment is kept; the Go statement {{ upper := ... }} computed a value used below; if, switch and for rendered the matching branches; the class list gained empty only when the slice was empty; the css component wrote a <style> block once and a generated class name; the script component wrote a function once and the button calls it with a JSON-quoted argument; disabled? was omitted and required? was written bare; the fragment rendered in place; and templ.Raw passed <hr/> through.

Syntax

You writeWhat it does
package main and Go imports at the topOrdinary Go, copied to the generated file
templ Name(a string, b int) { ... }A component: a Go function returning templ.Component
<p>{ expr }</p>Prints a string expression, HTML-escaped for its context
{{ x := f(y) }}A Go statement inside a component
// commentA Go comment, dropped from the output
<!-- comment -->An HTML comment, kept in the output
if cond { ... } else if c2 { ... } else { ... }Conditional markup
switch v { case "a": ... default: ... }Multiple branches
for i, x := range xs { ... }A loop
@Other(args)Call another component (on its own line)
@Layout("t") { ... }Call with children
{ children... }Render the children passed by the caller
@thingRender a templ.Component variable or parameter

Attributes

You writeResult
id={ expr }Attribute with an escaped string value
class={ "a", "b" }Class list from several strings
class={ "row", templ.KV("sold-out", cond) }Class added only when the condition is true
class={ highlight() }Use a css component's generated class
if cond { aria-current="page" } inside a tagConditional attribute
disabled?={ cond }Boolean attribute, present only when true
{ attrs... }Spread a templ.Attributes map onto the element
href={ templ.URL(s) }URL with javascript: and data: blocked
href={ templ.SafeURL(s) }URL passed through untouched; only for trusted strings
onclick={ confirmDelete(name) }Call a script component with JSON-encoded arguments
onclick={ templ.JSFuncCall("f", arg) }Call an existing JavaScript function safely

Helper functions in the templ package

FunctionUse
templ.Handler(c, opts...)Turn a component into an http.Handler
templ.WithStatus(code)Handler option: response status
templ.WithStreaming()Handler option: write as it renders, no buffering
templ.WithErrorHandler(f)Handler option: your own response on a render error
templ.WithFragments(ids...)Handler option: render only these fragments
templ.Fragment(id)Mark part of a component as an addressable fragment
templ.Flush()Component that flushes the writer
templ.Join(a, b, ...)Concatenate components
templ.Raw(html)Write HTML as given (unsafe for untrusted input)
templ.JSONScript(id, v)A script element holding safely encoded JSON
templ.NewOnceHandle()Render something once per page (scripts, styles)
templ.WithNonce(ctx, n) and templ.GetNonce(ctx)CSP nonce through the context
templ.ComponentFunc(f)Make a component from a Go function
templ.FromGoHTML(t, data) and templ.ToGoHTML(ctx, c)Bridge to html/template

Commands

CommandUse
go get -tool github.com/a-h/templ/cmd/[email protected]Pin the generator as a module tool
go tool templ generateGenerate *_templ.go for the current directory
go tool templ generate -f file.templOne file
go tool templ generate --watch --cmd="go run ."Regenerate on change and run a command
go tool templ fmt .Format .templ files in place
go tool templ lspStart the language server (an editor runs this)
go tool templ versionPrint the version

Gotchas

A component call must start its own line, or it is read as text (an inline @Count(n) after words printed literally). Expressions in braces must be strings, or return (string, error); format numbers with fmt.Sprint. templ.KV with a false condition can leave an empty class="", while a conditional attribute leaves nothing. Text-only edits hot reload in watch mode, but expression changes restart your command. A failed render after a flush leaves a truncated page with a 200 status. And generated files are build artifacts: regenerate after pulling, and again in CI.

Patterns worth copying

Pass plain values, not requests, into components. Put layouts in components that render { children... }, and named slots in templ.Component parameters. Choose the layout in the handler (full page or fragment). Keep one fragment per region HTMX will re-request. Render to a buffer unless you mean to stream. Test with a string builder and a golden file (testing). Generate in CI before build and test. Treat Raw, SafeURL and any user string in style as review flags.

The full syntax guide is at templ.guide, the generated API on pkg.go.dev, and the series continues with layouts, forms, HTMX and streaming.

Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.