
Templ
- 4 installs
- 19 repo stars
- Updated August 1, 2026
- xobotyi/cc-foundry
Helps with ai & agent building tasks.
About
templ is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- templ
- AI & Agent Building
- AI-coding skill
Templ by the numbers
- 4 all-time installs (skills.sh)
- Ranked #13,348 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xobotyi/cc-foundry --skill templAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 19 |
| Last updated | August 1, 2026 |
| Repository | xobotyi/cc-foundry ↗ |
What it does
Helps with ai & agent building tasks.
Files
templ
Type-safe Go HTML templating. Components defined in .templ files compile to Go functions returning templ.Component via templ generate. Outside templ blocks = ordinary Go. Inside = templ syntax.
References
Extended examples and detailed patterns for the rules below:
| Topic | Reference | Contents |
|---|---|---|
| Template syntax, expressions, control flow, raw Go blocks | ${CLAUDE_SKILL_DIR}/references/syntax.md | File structure, expression types, error propagation, auto-escaping, control flow examples |
| Component definition, composition, children, fragments | ${CLAUDE_SKILL_DIR}/references/components.md | Component interface, @ composition, children context API, render-once, fragment rendering |
| Boolean, conditional, spread attributes, key expressions | ${CLAUDE_SKILL_DIR}/references/attributes.md | Attribute types with code examples, spread value table, URL/JS/JSON attribute patterns |
| View models, layouts, context, html/template interop | ${CLAUDE_SKILL_DIR}/references/patterns.md | Props struct pattern, nested layouts, context helpers with middleware, Go template interop |
| Script/style tags, inline events, data passing to JS | ${CLAUDE_SKILL_DIR}/references/javascript.md | JSFuncCall/JSExpression/JSONString/JSONScript API, IIFE pattern, method summary table |
| Class patterns, CSS components, style attributes | ${CLAUDE_SKILL_DIR}/references/styling.md | Class toggling approaches (KV, maps, raw Go), CSS component scoping, style sanitization |
Syntax
Expressions { }
Output Go values inside templ blocks. Content is automatically HTML-escaped for XSS safety.
Supported types: string, numbers (int, uint, float32, complex64, etc.), booleans, and any type based on these (e.g. type Name string).
Use variables, field access, and function calls: { name }, { p.Name }, { strings.ToUpper(name) }, { fmt.Sprintf("%d items", count) }.
Functions returning (T, error) propagate errors to Render() with source location info.
Elements
All tags must close. Write <br/> not <br>. templ is aware of void elements and strips / in output HTML, but source must always include it.
templ automatically minifies HTML output.
Control Flow
Use bare Go keywords — if/else, switch/case, for/range. No special syntax.
Text starting with if, for, or switch triggers the parser. Two solutions:
- Wrap as expression:
{ "if you need this text" } - Capitalize the keyword:
If you need this text
Raw Go {{ }}
Scoped Go statements inside templ blocks for intermediate variables.
{{ total := calculateTotal(items) }}
<p>Total: { fmt.Sprintf("%d", total) }</p>Use {{ }} to avoid calling expensive functions twice — cache results in a variable.
Comments
- Inside templ blocks: use HTML comments
<!-- -->(rendered to output). No nesting. - Outside templ blocks: use Go comments
//(not rendered).
Implicit Variables
Every component has two implicit variables:
- `ctx` —
context.Contextfrom theRendercall. Available in all components. - `children` — content passed via
@component() { ... }. Access with{ children... }.
Components
Definition and Visibility
Components compile to Go functions returning templ.Component. Follow Go visibility rules: uppercase name = exported, lowercase = unexported.
The templ.Component interface: Render(ctx context.Context, w io.Writer) error.
Partial output warning: a component may write partial output to io.Writer before returning an error. To guarantee all-or-nothing, render to a buffer first.
Composition
Call components with @ prefix: @header(), @components.Header(), @nav.Item("Home", "/").
Children
Pass content to a component with @layout() { <p>children</p> }. Receive with { children... } in the component body.
In code-only components, manage children via context: templ.WithChildren, templ.GetChildren, templ.ClearChildren.
Components as Parameters
Pass components as values via templ.Component type parameters, render with @param.
Joining Components
Aggregate multiple components into one with @templ.Join(header(), nav(), footer()).
Method Components
Attach components to types when a component has many configuration options — struct fields are self-documenting and can have defaults. Call inline: @Button{Text: "Submit", Variant: "primary"}.Render().
Code-Only Components
Implement templ.Component in pure Go using templ.ComponentFunc:
func button(text string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, "<button>"+templ.EscapeString(text)+"</button>")
return err
})
}In code-only components, you must escape HTML yourself with templ.EscapeString. Auto- escaping only applies inside .templ files.
Render-Once
Ensure content renders once per HTTP response (or per context). Common use: shared <script>, <style>, or <link> tags.
1. Declare handles at package level: var h = templ.NewOnceHandle(). 2. Use in component: @h.Once() { <script src="..."></script> }. 3. Never inline @templ.NewOnceHandle().Once() — creates a new handle each call, content renders every time, defeating the purpose.
For cross-package shared dependencies, export render-once components — wrap the handle and Once() call in an exported templ function, then call from any package.
Fragments
Render subsections of templates, discarding all other output. The full template still executes (all logic runs), but only the fragment's output is written.
- Define:
@templ.Fragment("content") { <div>fragment</div> } - Render via HTTP:
templ.Handler(Page(), templ.WithFragments("content")) - Render without HTTP:
templ.RenderFragments(ctx, w, Page(), "content")
Custom fragment keys: use typed keys (type contentKey struct{}) to avoid name clashes.
Nested fragments: selecting outer includes inner. Useful for partial page updates (e.g. with htmx).
Attributes
Constant Attributes
Standard HTML attributes with double quotes: <p class="container" data-testid="p">.
Dynamic String Attributes
Set to Go expressions with { }: <div class={ className }>, <div data-id={ fmt.Sprintf("item-%d", id) }>.
String values are automatically HTML-attribute-encoded. Functions returning (string, error) propagate errors to Render().
Boolean Attributes ?=
Presence/absence based on Go boolean: <input disabled?={ isDisabled }/>, <button hidden?={ !showButton }/>. Static booleans: <hr noshade/>.
Conditional Attributes
Use if inside element open tags to conditionally add attributes. The conditional attribute replaces the earlier one of the same name — include base classes in both branches.
Attribute Key Expressions
Dynamically set the attribute key: <p { "data-" + suffix }="value">.
Warning: key expressions don't get type-specific handling. URL attributes (href) and event handlers (on*) defined via key expressions are treated as plain strings without special sanitization.
Spread Attributes
Append a dynamic map with { attrs... } where attrs is templ.Attributes (map[string]any).
| Value Type | Rendering |
|---|---|
string | name="value" |
bool | name (if true) or omitted (if false) |
templ.KeyValue[string, bool] | name="value" if bool is true |
templ.KeyValue[bool, bool] | name if both bools are true |
Spread attributes can be conditional using if inside element open tags.
Never mutate a global `templ.Attributes` var — create fresh templ.Attributes{} per render call.
Security
HTML Auto-Escaping
All expressions { } are HTML-escaped. Use @templ.Raw() only for trusted content.
URL Sanitization
href, src, action auto-sanitize dynamic values — javascript: schemes become about:invalid#TemplFailedSanitizationURL. Bypass with templ.SafeURL() for trusted URLs.
Constant URL values are NOT sanitized: <a href="javascript:..."> renders as-is.
For non-standard URL attributes (e.g. htmx hx-get), use templ.URL() which sanitizes without the special href/src/action behavior.
CSS Sanitization
Dynamic CSS values are sanitized by default. Unsafe property names become zTemplUnsafeCSSPropertyName, unsafe values become zTemplUnsafeCSSPropertyValue. Bypass with templ.SafeCSS (full declaration) or templ.SafeCSSProperty (single value).
JS Sanitization
Function names in templ.JSFuncCall are sanitized — invalid names become __templ_invalid_function_name. templ.JSExpression bypasses encoding entirely — only use with trusted compile-time constants like "event" or "this". templ.JSUnsafeFuncCall skips function name sanitization — never use with user-provided input.
Styling
Class Attribute
Multiple approaches for conditional classes, ordered by simplicity:
| Pattern | Syntax | Best For |
|---|---|---|
| Static string | class="button primary" | Unchanging classes |
| Dynamic expression | class={ className } | Single dynamic class |
| Multiple values | class={ "button", className } | Combining static + dynamic |
templ.KV | class={ "btn", templ.KV("active", isActive) } | Conditional single class |
map[string]bool | class={ map[string]bool{"tab": true, "active": isActive} } | Multiple conditional |
Raw Go {{ }} | Compute class string in {{ }}, use in class={ computed } | Complex logic |
| Conditional attribute | if cond { class="full-set" } in open tag | Replacing full value |
CSS component functions (from css blocks) can be used in class expressions: class={ "button", templ.KV(primaryButton(), isPrimary) }.
Style Attribute
Dynamic styles accept multiple values combined in output: style={ style1, style2 }.
| Type | Example |
|---|---|
string | "background-color: red" |
templ.SafeCSS | Bypasses sanitization |
map[string]string | map[string]string{"color": "red"} |
map[string]templ.SafeCSSProperty | Map with unsanitized values |
templ.KeyValue[string, bool] | Conditional: include CSS string if true |
templ.KeyValue[templ.SafeCSS, bool] | Conditional unsanitized CSS |
| Functions returning any above | Single function may return (T, error) |
Use templ.KV("border-color: red", hasError) for conditional style toggling.
Use map[string]string for computed style sets from Go functions.
CSS Components
Define scoped CSS with auto-generated hash-based class names:
css primaryButton() {
background-color: #ffffff;
color: { red };
}Key behaviors:
- Class names are auto-generated (hash-based) — don't rely on them being stable
- CSS is rendered as
<style>tags, once per HTTP request per unique class - Dynamic values inside
cssblocks use{ expr }syntax - CSS components accept arguments — each unique argument combination generates a separate class
- Dynamic property names/values are sanitized; bypass with
templ.SafeCSSProperty
Raw <style> Elements
Raw <style> tags render without modification. Use CSS components instead if you need once-per-request deduplication.
CSS Middleware
templ.NewCSSMiddleware serves a global stylesheet instead of inline <style> tags. See ${CLAUDE_SKILL_DIR}/references/styling.md.
JavaScript Integration
Script Tags
Standard <script> tags for client-side JavaScript. Use templ.OnceHandle to render a script only once per response.
Passing Data: Go to JavaScript
Three approaches, ordered by preference:
| Approach | API | Best For |
|---|---|---|
| Data attributes | data-config={ templ.JSONString(data) } | Component-scoped data |
| Script elements | @templ.JSONScript("id", data) | Page-level configuration |
| Inline interpolation | {{ value }} in <script> | Least preferred — mixing data/code |
templ.JSFuncCall
Call a client-side function with server-side data. Arguments are JSON-encoded.
Use in attributes: <button onclick={ templ.JSFuncCall("alert", msg) }>. Use as standalone script: @templ.JSFuncCall("initApp", config.Name) renders <script>initApp("MyApp");</script>.
templ.JSExpression
Bypass JSON encoding for raw JS expressions: templ.JSExpression("event"), templ.JSExpression("this"). Only use with trusted compile-time constants — output goes directly to HTML without encoding.
templ.JSONString
Encode Go data as JSON string for HTML attributes: <div x-data={ templ.JSONString(data) }>. Client reads with JSON.parse(el.getAttribute('attr')).
templ.JSONScript
Create <script type="application/json"> element: @templ.JSONScript("id", data). Client reads with JSON.parse(document.getElementById('id').textContent).
Inline {{ }} Interpolation in Scripts
Inside JS strings: value is string-escaped. Outside JS strings: value is JSON-encoded. templ auto-escapes both. Prefer `templ.JSONString` or `templ.JSONScript` over inline interpolation — separating data from code is easier to maintain.
templ.JSUnsafeFuncCall
Identical to templ.JSFuncCall but skips function name sanitization. Arguments still JSON-encoded. Never use with user-provided input.
IIFE Pattern
Prevent variable leaking into global scope by wrapping in (() => { ... })(). Use document.currentScript for DOM traversal relative to the script element.
Best Practice: Avoiding Inline Event Handlers
Separate behavior from markup: load shared functions via templ.OnceHandle, pass data via data-* attributes, isolate in IIFEs. See ${CLAUDE_SKILL_DIR}/references/javascript.md.
External Scripts
Import via <script src="...">. For TypeScript/NPM projects, bundle with esbuild into a single JS file, then reference via script tag.
Patterns
View Models
Create a props struct with a NewProps(domain) constructor in Go. Templates receive only pre-transformed data — no I/O, no complex logic. Test view models as pure Go functions.
Layout Pattern
Use children for content injection: define a layout component that renders { children... }, then wrap page content with @BaseLayout("title") { ... }. Compose by nesting layouts.
For multiple content slots, pass templ.Component as parameters alongside children: templ TwoColumnLayout(sidebar templ.Component) uses @sidebar and { children... }.
Context for Cross-Cutting Data
For auth, theme, locale — use Go context with private key types and type-safe With*(ctx, value) / Get*(ctx) (T, bool) helpers. Set in HTTP middleware, access via implicit ctx in templates. Prefer prop drilling for direct parent-child data. Always use type-safe getters — direct ctx.Value(key).(Type) panics on missing keys.
Interop with html/template
@templ.FromGoHTML(goTemplate, data) embeds Go templates in templ. templ.ToGoHTML(ctx, component) embeds templ in Go templates. Useful for gradual migration.
Toolchain
templ generate # compile all .templ -> Go
templ generate -f file.templ # single file
templ generate -watch # watch mode (dev only, not optimized for production)
templ fmt . # format all .templ filesThe -watch flag regenerates on file change. Combine with -cmd and -proxy for live-reload during development.
Run templ generate after every .templ change. Generated *_templ.go files must be committed.
Testing
Expectation Testing
Render to pipe, parse with goquery, assert with CSS selectors:
r, w := io.Pipe()
go func() {
_ = myComponent(data).Render(context.Background(), w)
_ = w.Close()
}()
doc, err := goquery.NewDocumentFromReader(r)
// Assert with doc.Find(`[data-testid="myComponent"]`)Use data-testid attributes for reliable test selectors.
Snapshot Testing
Compare rendered output against expected HTML using //go:embed expected.html and htmldiff.Diff(component, expected).
Testing Principles
- Component-level tests: verify data renders correctly using
data-testidselectors - Page-level tests: verify components are present (by
data-testid), don't re-test
component internals
- Test view models in pure Go — no rendering needed for data transformation logic
Application
When writing templ code:
- Apply all conventions silently — don't narrate each rule being followed
- If an existing codebase contradicts a convention, follow the codebase and flag the
divergence once
- Keep templates focused on rendering. Move data transformation, validation, and business
logic to Go code
- Run
templ generateafter every.templchange before attempting to build or test
When reviewing templ code:
- Cite the specific violation and show the fix inline
- Don't lecture or quote the rule — state what's wrong and how to fix it
Integration
The golang skill governs Go implementation; this skill governs .templ file authoring.
Templates are renderers, not processors. When in doubt, move logic to Go.
{
"sources": {
"Basic Syntax": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/01-basic-syntax.md",
"Elements": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/02-elements.md",
"Attributes": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/03-attributes.md",
"Expressions": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/04-expressions.md",
"Statements": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/05-statements.md",
"If Else": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/06-if-else.md",
"Switch": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/07-switch.md",
"Loops": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/08-loops.md",
"Raw Go": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/09-raw-go.md",
"Template Composition": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/10-template-composition.md",
"Forms": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/11-forms.md",
"CSS Style Management": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/12-css-style-management.md",
"Script Templates": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/13-script-templates.md",
"Comments": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/14-comments.md",
"Context": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/15-context.md",
"Using With Go Templates": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/16-using-with-go-templates.md",
"Rendering Raw HTML": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/17-rendering-raw-html.md",
"Render Once": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/18-render-once.md",
"Fragments": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/19-fragments.md",
"Using React With Templ": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/03-syntax-and-usage/20-using-react-with-templ.md",
"Components": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/04-core-concepts/01-components.md",
"Template Generation": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/04-core-concepts/02-template-generation.md",
"Testing": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/04-core-concepts/03-testing.md",
"View Models": "https://raw.githubusercontent.com/a-h/templ/main/docs/docs/04-core-concepts/04-view-models.md",
"LLM Reference (full docs)": "https://templ.guide/llms.md"
},
"lastFetched": "2026-02-16T08:47:44.035Z"
}
Attributes Reference
Constant Attributes
Standard HTML attributes with double quotes:
<p data-testid="paragraph" class="container">Text</p>Dynamic String Attributes
Set attributes to Go expressions using { }:
<div class={ className }></div>
<div data-id={ fmt.Sprintf("item-%d", id) }></div>String values are automatically HTML-attribute-encoded (<, >, &, quotes become HTML entities). This doesn't affect behavior.
Functions returning (string, error) propagate errors to Render():
// If testID() returns error, Render() returns it with location
<p data-testid={ testID(true) }>Text</p>Boolean Attributes ?=
Presence = true, absence = false. Use ?= for dynamic booleans:
// Static boolean
<hr noshade/>
// Dynamic boolean — renders <input disabled> or <input> based on value
<input disabled?={ isDisabled }/>
<button hidden?={ !showButton }/>Conditional Attributes
Use if inside element open tags to conditionally add attributes:
<div
class="base"
if isHighlighted {
class="highlighted"
}
></div>Note: the conditional attribute replaces the earlier one of the same name.
Attribute Key Expressions
Dynamically set the attribute key:
<p { "data-" + suffix }="value">Text</p>Warning: Key expressions don't get type-specific handling. URL attributes (href) and event handlers (on*) defined via key expressions are treated as plain strings without special sanitization.
Spread Attributes
Append a dynamic map of attributes using { attrs... }:
templ Button(attrs templ.Attributes) {
<button { attrs... }>Click</button>
}
// Usage:
@Button(templ.Attributes{
"class": "btn-primary",
"disabled": true,
})templ.Attributes is map[string]any. Value behavior:
| Value Type | Rendering |
|---|---|
string | name="value" |
bool | name (if true) or omitted (if false) |
templ.KeyValue[string, bool] | name="value" if bool is true |
templ.KeyValue[bool, bool] | name if both bools are true |
Spread attributes can be conditional:
<hr
if shouldApply {
{ attrs... }
}
/>URL Attributes
href, src, action auto-sanitize dynamic values. Dangerous schemes like javascript: are replaced with about:invalid#TemplFailedSanitizationURL.
// Auto-sanitized (safe)
<a href={ userProvidedURL }>Link</a>
// Bypass sanitization — trusted source only
<a href={ templ.SafeURL(trustedURL) }>Link</a>Constant values are NOT sanitized: <a href="javascript:alert('hi')"> renders as-is.
Non-Standard URL Attributes
For URL-containing attributes not recognized by templ (e.g. htmx hx-get):
<div hx-get={ templ.URL(fmt.Sprintf("/api/%s", id)) }></div>templ.URL() sanitizes the URL without the special behavior of href/src/action.
JavaScript Attributes
onClick and other on* handlers accept templ.JSFuncCall expressions:
// Call a client-side function with server data
<button onclick={ templ.JSFuncCall("alert", "Hello") }>Click</button>
// Passing event objects with templ.JSExpression
<button onclick={
templ.JSFuncCall("handler", templ.JSExpression("event"))
}>Click</button>Warning: templ.JSExpression bypasses JSON encoding — output goes directly to HTML. Only use with trusted, compile-time constants like "event" or "this".
JSON Attributes
For attributes expecting JSON data (htmx hx-vals, Alpine x-data):
<div x-data={ templ.JSONString(data) }>Content</div>
<button alert-data={ templ.JSONString(payload) }>Show</button>Or serialize manually:
func countriesJSON() string {
countries := []string{"Czech Republic", "Slovakia"}
bytes, _ := json.Marshal(countries)
return string(bytes)
}<search-component suggestions={ countriesJSON() }/>Components Reference
Component Definition
Components compile to Go functions returning templ.Component:
templ headerTemplate(name string) {
<header data-testid="headerTemplate">
<h1>{ name }</h1>
</header>
}Generated Go:
func headerTemplate(name string) templ.Component {
// Generated contents
}The templ.Component Interface
type Component interface {
Render(ctx context.Context, w io.Writer) error
}Components follow Go visibility rules: uppercase name = exported (public), lowercase = unexported (private). Share components across packages by exporting them.
Partial output warning: A component may write partial output to io.Writer before returning an error. To guarantee all-or-nothing, render to a buffer first.
Composition with @
Call components with the @ prefix:
@header() // same package
@components.Header() // imported package
@nav.Item("Home", "/") // with argsChildren
Pass content to a component:
@layout() {
<p>This becomes children</p>
}Receive children with { children... }:
templ layout() {
<main>
{ children... }
</main>
}Children in Code-Only Components
Children are passed via context:
// Pass children
ctx := templ.WithChildren(context.Background(), childComponent)
wrapChildren().Render(ctx, os.Stdout)
// Get children
children := templ.GetChildren(ctx)
// Prevent passing children further down
ctx = templ.ClearChildren(ctx)Components as Parameters
Pass components as values:
templ wrapper(content templ.Component) {
<div class="wrapper">
@content
</div>
}
// Usage in templates:
@wrapper(someComponent())
// Usage in Go:
c := paragraph("Dynamic contents")
layout(c).Render(ctx, os.Stdout)Joining Components
Aggregate multiple components into one:
@templ.Join(header(), nav(), footer())Method Components
Attach components to types:
type Button struct {
Text string
Variant string
Disabled bool
}
templ (b Button) Render() {
<button
class={ "btn btn-" + b.Variant }
disabled?={ b.Disabled }
>
{ b.Text }
</button>
}Call inline:
@Button{Text: "Submit", Variant: "primary"}.Render()Code-Only Components
Implement templ.Component in pure Go using templ.ComponentFunc:
func button(text string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, "<button>"+templ.EscapeString(text)+"</button>")
return err
})
}Warning: In code-only components, you must escape HTML yourself using templ.EscapeString. The automatic escaping only applies to .templ files.
Sharing Components
Components follow Go package rules:
- Same package: all components in the same directory are accessible to each other.
- Cross-package: export by capitalizing name, then import the package.
// components/header.templ — exported
package components
templ Header() {
<header>Header</header>
}// pages/home.templ — importing
package pages
import "myapp/components"
templ Home() {
@components.Header()
}Cross-module: go get <module> first, then import.
Render-Once
Ensure content renders once per HTTP response (or per context):
var chartHandle = templ.NewOnceHandle()
templ Chart(data []Point) {
@chartHandle.Once() {
<script src="/js/charts.js"></script>
}
<div class="chart" data-points={ templ.JSONString(data) }></div>
}Critical: Declare the handle at package level. Never inline:
// WRONG — new handle each time, content renders every time
@templ.NewOnceHandle().Once() { ... }
// CORRECT — reuses same handle
var handle = templ.NewOnceHandle()
@handle.Once() { ... }Cross-Package Dependencies
Export render-once components for shared dependencies:
// pkg/deps/deps.templ
package deps
var jqueryHandle = templ.NewOnceHandle()
templ JQuery() {
@jqueryHandle.Once() {
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
}
}// pkg/widget/widget.templ — jQuery included once regardless of how many widgets
package widget
import "myapp/pkg/deps"
templ Slider() {
@deps.JQuery()
<div class="slider">...</div>
}Common use cases: shared <script>, <style>, or <link> tags.
Fragments
Render subsections of templates, discarding all other output:
templ Page() {
<div>Page Header</div>
@templ.Fragment("content") {
<div>Only this part can be rendered separately</div>
}
}Rendering Fragments
With HTTP handler:
handler := templ.Handler(Page(), templ.WithFragments("content"))Without HTTP handler (e.g. static generation):
w := new(bytes.Buffer)
err := templ.RenderFragments(ctx, w, Page(), "content")The full template is still executed (all logic runs), but only the fragment's output is written.
Custom Fragment Keys
Avoid name clashes with typed keys:
type contentKey struct{}
var Content = contentKey{}
templ Page() {
@templ.Fragment(Content) {
<div>Fragment content</div>
}
}Nested Fragments
Fragments can nest. Selecting the outer fragment includes inner fragments:
templ Page() {
@templ.Fragment("outer") {
<div>Outer Start</div>
@templ.Fragment("inner") {
<div>Inner Content</div>
}
<div>Outer End</div>
}
}Fragments are particularly useful for partial page updates (e.g. with htmx).
JavaScript Reference
Script Tags
Standard <script> tags for client-side JavaScript:
templ page() {
<script>
function handleClick(event) {
alert(event + ' clicked');
}
</script>
<button onclick="handleClick(this)">Click me</button>
}To render a <script> tag only once per response, use templ.OnceHandle (see components reference for render-once details).
Passing Go Data to JavaScript
templ.JSFuncCall — Call Client Functions
Call a client-side function with server-side data. Arguments are JSON-encoded:
templ Alert(data CustomType) {
<button onclick={ templ.JSFuncCall("alert", data.Message) }>Show</button>
}Output:
<button onclick="alert('Hello, from Go')">Show</button>Invalid function names (containing </script> or expressions) are sanitized to __templ_invalid_function_name.
Render as a standalone <script> element:
@templ.JSFuncCall("initApp", config.Name, config.Version)Output:
<script>initApp("MyApp", 42);</script>templ.JSExpression — Raw JS Expressions
Bypass JSON encoding for event, this, or other JS expressions:
<button onclick={
templ.JSFuncCall("handler", templ.JSExpression("event"), "message")
}>Click</button>Output:
<button onclick="handler(event, 'message')">Click</button>Security warning: templ.JSExpression outputs directly to HTML without encoding. Only use with trusted compile-time constants.
templ.JSONString — Data in Attributes
Encode Go data as a JSON string for HTML attributes:
<div x-data={ templ.JSONString(data) }>Content</div>
<button alert-data={ templ.JSONString(payload) }>Show</button>Client-side access:
const data = JSON.parse(button.getAttribute('alert-data'));templ.JSONScript — Data in Script Elements
Create a <script type="application/json"> element with JSON data:
@templ.JSONScript("app-config", config)Output:
<script id="app-config" type="application/json">{"key":"value"}</script>Client-side access:
const config = JSON.parse(document.getElementById('app-config').textContent);Inline {{ }} Interpolation in Scripts
Interpolate Go data directly within <script> tags:
templ greeting(name string) {
<script>
// Inside JS strings — string-escaped
const message = "Hello, {{ name }}";
// Outside strings — JSON-encoded
const data = {{ name }};
</script>
}Behavior differs by context:
- Inside JS strings: value is string-escaped
- Outside JS strings: value is JSON-encoded (quoted string, number, etc.)
templ auto-escapes to prevent XSS in both cases.
Prefer `templ.JSONString` or `templ.JSONScript` over inline interpolation — separating data from code is easier to maintain and debug.
IIFE Pattern for Scope Isolation
Prevent variables from leaking into global scope:
templ Interactive() {
<div id="widget">...</div>
<script>
(() => {
const widget = document.getElementById('widget');
// Private scope — variables don't leak
})();
</script>
}Avoiding Inline Event Handlers
Best practice: separate behavior from markup using templ.OnceHandle + data-* attributes + IIFE:
var helloHandle = templ.NewOnceHandle()
templ hello(label, name string) {
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
(() => {
let el = document.currentScript.closest('div');
let btn = el.querySelector('input[data-name]');
btn.addEventListener('click', function() {
hello(btn.getAttribute('data-name'));
});
})()
</script>
</div>
}This pattern: 1. Loads the shared function once via templ.OnceHandle 2. Passes server data via data-* attributes 3. Isolates initialization in an IIFE 4. Uses document.currentScript for DOM traversal relative to the script
templ.JSUnsafeFuncCall — Bypass Sanitization
Identical to templ.JSFuncCall but skips function name sanitization. Arguments are still JSON-encoded. Use only when the sanitizer incorrectly rejects a valid function name.
Never use with user-provided input. The function name is written directly to HTML output.
Importing External Scripts
templ head() {
<head>
<script src="https://cdn.example.com/lib.js"></script>
<script src="/assets/js/app.js"></script>
</head>
}For TypeScript/NPM projects, use esbuild to bundle into a single JS file, then reference via <script src="...">.
Method Summary
| Method | Purpose | Encoding |
|---|---|---|
templ.JSFuncCall | Call JS function with Go data | JSON-encoded args |
templ.JSExpression | Raw JS expression (event, this) | None — raw output |
templ.JSONString | Go data → JSON string for attributes | JSON + HTML-encoded |
templ.JSONScript | Go data → <script type="application/json"> | JSON |
{{ value }} in scripts | Inline interpolation | Context-dependent |
templ.JSUnsafeFuncCall | Bypass function name sanitization | None — security risk |
Patterns Reference
View Models
Separate display data from domain models. Keep template logic minimal:
// Go file — data transformation
type CardProps struct {
Title string
Description string
ImageURL string
Actions []Action
}
func NewCardProps(product Product) CardProps {
return CardProps{
Title: product.Name,
Description: truncate(product.Desc, 100),
ImageURL: product.PrimaryImage(),
Actions: productActions(product),
}
}// Template — pure rendering, no business logic
templ Card(props CardProps) {
<div class="card">
<img src={ props.ImageURL }/>
<h3>{ props.Title }</h3>
<p>{ props.Description }</p>
for _, action := range props.Actions {
@ActionButton(action)
}
</div>
}Benefits:
- Template logic stays minimal — no database calls or complex transformations
- Easy to test — just test
NewCardPropsin pure Go - Props struct documents what the template needs
Layout Pattern
Use children for content injection:
templ BaseLayout(title string) {
<!DOCTYPE html>
<html>
<head>
<title>{ title }</title>
</head>
<body>
{ children... }
</body>
</html>
}
templ Page() {
@BaseLayout("Home") {
<main>
<h1>Welcome</h1>
</main>
}
}Nested Layouts
Compose layouts by nesting:
templ AppLayout() {
@BaseLayout("App") {
@NavBar()
<div class="content">
{ children... }
</div>
@Footer()
}
}
templ DashboardPage() {
@AppLayout() {
<h1>Dashboard</h1>
}
}Multiple Slots
Pass components as parameters for multiple content areas:
templ TwoColumnLayout(sidebar templ.Component) {
<div class="layout">
<aside>@sidebar</aside>
<main>{ children... }</main>
</div>
}Context for Cross-Cutting Data
Avoid prop drilling for auth, theme, locale. Use Go's context package.
Define Context Helpers
type contextKey string
var userKey = contextKey("user")
func WithUser(ctx context.Context, u User) context.Context {
return context.WithValue(ctx, userKey, u)
}
func GetUser(ctx context.Context) (User, bool) {
u, ok := ctx.Value(userKey).(User)
return u, ok
}Use in Templates
templ components have an implicit ctx variable:
templ NavBar() {
{{ user, ok := GetUser(ctx) }}
<nav>
if ok {
<span>{ user.Name }</span>
} else {
<a href="/login">Login</a>
}
</nav>
}Set via Middleware
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := getUserFromSession(r)
ctx := WithUser(r.Context(), user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Context Guidelines
- Prefer prop drilling for direct parent-child data — it's simpler and type-safe.
- Use context for cross-cutting concerns (auth, theme, locale) that many components need.
- Always use type-safe getters — direct
ctx.Value(key).(Type)panics on missing keys. - Private key types — use
type contextKey stringto avoid collisions. - Context is not strongly typed; errors only show at runtime, not compile time.
Conditional Classes
See styling.md for all class toggling approaches: templ.KV, maps, raw Go blocks, and conditional attributes.
Passing Data to JavaScript
Three approaches, ordered by preference:
1. Data attributes — data-config={ templ.JSONString(data) }. Preferred for component-scoped data. 2. Script elements — @templ.JSONScript("id", data). Best for page-level configuration. 3. Inline interpolation — {{ value }} inside <script>. Least preferred — mixing data and code is harder to maintain.
See javascript.md for API details and examples.
Interop with html/template
Use Go Templates in templ
Embed existing html/template in templ components:
import "html/template"
var goTemplate = template.Must(template.New("ex").Parse("<div>{{ . }}</div>"))
templ Page() {
<body>
@templ.FromGoHTML(goTemplate, "Hello, World!")
</body>
}Use templ in Go Templates
Convert templ component to template.HTML:
templComponent := greeting()
html, err := templ.ToGoHTML(context.Background(), templComponent)
// Use html in text/html template
err = goTemplate.Execute(os.Stdout, html)Useful for gradual migration from html/template to templ.
Method Components Pattern
Use method components when a component has many configuration options — struct fields are self-documenting and can have defaults. See components.md for definition syntax and examples.
Styling Reference
Class Attribute
Static Classes
<button class="button is-primary">Click</button>Dynamic Classes
Pass Go expressions:
<button class={ className }>Click</button>
<button class={ "button", className }>Click</button>The class expression accepts multiple values — all are added to output.
Conditional Classes with templ.KV
Toggle classes based on booleans:
<button class={
"button",
templ.KV("is-primary", isPrimary),
templ.KV("is-disabled", isDisabled),
}>Click</button>templ.KV adds the class only when the boolean is true.
Conditional Classes with Maps
<div class={ map[string]bool{
"tab": true,
"tab-active": isActive,
} }></div>Conditional Classes with Raw Go
Use {{ }} blocks for complex class logic:
templ Tab(label string, active bool) {
{{
class := "tab"
if active {
class += " tab-active"
}
}}
<div class={ class }>{ label }</div>
}Conditional Classes with Conditional Attributes
Use if inside element open tags:
templ Tab(label string, active bool) {
<div
class="tab"
if active {
class="tab tab-active"
}
>
{ label }
</div>
}Note: the conditional attribute replaces the previous value entirely — include base classes in both branches.
CSS Component Classes
CSS components (see below) can be used in class expressions:
css red() {
background-color: #ff0000;
}
<button class={ "button", templ.KV(red(), isPrimary) }>Click</button>Style Attribute
Static Styles
<button style="background-color: red">Click</button>Dynamic Styles
Multiple values are combined in output:
<button style={ style1, style2 }>Click</button>Supported types for style values:
| Type | Example |
|---|---|
string | "background-color: red" |
templ.SafeCSS | Bypasses sanitization |
map[string]string | map[string]string{"color": "red"} |
map[string]templ.SafeCSSProperty | Map with unsanitized values |
templ.KeyValue[string, bool] | Conditional: include CSS if true |
templ.KeyValue[templ.SafeCSS, bool] | Conditional unsanitized CSS |
| Functions returning any above | Single function may return (T, error) |
Map Pattern
Useful for computed style sets:
func getProgressStyle(percent int) map[string]string {
return map[string]string{
"width": fmt.Sprintf("%d%%", percent),
"transition": "width 0.3s ease",
}
}<div style={ getProgressStyle(75) } class="progress-bar"></div>KeyValue Pattern
Conditional style toggling:
<input
type="text"
style={
templ.KV("border-color: #ff3860", hasError),
templ.KV("background-color: #fff5f7", hasError),
"padding: 0.5em 1em;",
}
/>Bypassing Style Sanitization
Dynamic CSS values are sanitized by default. Bypass with templ.SafeCSS:
func positionStyles(x, y int) templ.SafeCSS {
return templ.SafeCSS(fmt.Sprintf(
"transform: translate(%dpx, %dpx);", x*2, y*2,
))
}<div style={ positionStyles(10, 20) }>Drag me</div>Sanitized dangerous values become zTemplUnsafeCSSPropertyValue.
CSS Components
Define scoped CSS with auto-generated class names:
css primaryButton() {
background-color: #ffffff;
color: { red };
}
css secondaryButton() {
background-color: #ffffff;
color: { blue };
}
templ Button(text string, isPrimary bool) {
<button class={
"button",
secondaryButton(),
templ.KV(primaryButton(), isPrimary),
}>{ text }</button>
}Output:
<style type="text/css">.primaryButton_f179{background-color:#ffffff;color:#ff0000;}</style>
<button class="button primaryButton_f179">Click</button>Key behaviors:
- Class names are auto-generated (hash-based) — don't rely on them being stable.
- CSS is rendered as
<style>tags, once per HTTP request per unique class. - Dynamic values inside
cssblocks use{ expr }syntax.
CSS Components with Arguments
css loading(percent int) {
width: { fmt.Sprintf("%d%%", percent) };
}
templ ProgressBar() {
<div class={ loading(50) }></div>
<div class={ loading(100) }></div>
}Each unique argument combination generates a separate class.
CSS Sanitization
Dynamic property names and values in css blocks are sanitized:
- Unsafe property names →
zTemplUnsafeCSSPropertyName - Unsafe property values →
zTemplUnsafeCSSPropertyValue
Bypass with templ.SafeCSSProperty:
css rotation(degrees float64) {
transform: { templ.SafeCSSProperty(fmt.Sprintf("rotate(%ddeg)", int(degrees))) };
}<style> Elements
Raw <style> tags render without modification:
templ page() {
<style type="text/css">
p { font-family: sans-serif; }
.button { background-color: black; }
</style>
<p>Content</p>
}Use CSS components instead if you need once-per-request deduplication.
CSS Middleware
templ can serve a global stylesheet instead of inline <style> tags:
c1 := primaryButton()
handler := templ.NewCSSMiddleware(httpRoutes, c1)This adds a /styles/templ.css route. Include via <link rel="stylesheet" href="/styles/templ.css"> in your HTML.
Saves bandwidth by serving CSS once instead of per-request <style> tags.
Pattern Summary
| Pattern | Best For |
|---|---|
| Static string | Simple, unchanging classes/styles |
templ.KV | Conditional toggling of single class/style |
map[string]bool | Multiple conditional classes |
Raw Go {{ }} block | Complex class logic with intermediate variables |
| Conditional attribute | Replacing full attribute value based on condition |
map[string]string | Computed style sets |
css blocks | Component-scoped CSS with deduplication |
templ.SafeCSS | Trusted dynamic CSS bypassing sanitization |
Syntax Reference
File Structure
templ files use .templ extension. They start with a package name and imports, like Go.
package main
import "fmt"
import "strings"
// Ordinary Go code outside components
var greeting = "Welcome!"
templ MyComponent(name string) {
<div>{ name }</div>
}Outside templ blocks = ordinary Go. Inside = templ syntax.
Expressions { }
Output Go values inside templ blocks. Content is automatically HTML-escaped.
Supported types: string, numbers (int, uint, float32, complex64, etc.), booleans, and any type based on these (e.g. type Name string).
// Literals
<div>{ "print this" }</div>
<div>{ `backtick string` }</div>
<div>Number: { 42 }</div>
// Variables
<div>{ name }</div>
<div>{ p.Name }</div>
// Function calls
<div>{ strings.ToUpper(name) }</div>
<div>{ fmt.Sprintf("%d items", count) }</div>Error Propagation
Functions returning (T, error) propagate errors to Render():
// If getString() returns an error, Render() returns that error
// with source location information
<div>{ getString() }</div>Auto-Escaping
All expressions are HTML-escaped. Dangerous content is neutralized:
// Input: `</div><script>alert('xss')</script>`
// Output: </div><script>alert('xss')</script>
<div>{ userInput }</div>Elements
templ elements render HTML. All tags must be closed.
<div>Content</div> // standard element
<img src="test.png"/> // self-closing (void element)
<br/> // void — templ outputs <br> without /templ is aware of void elements (br, hr, img, input, etc.) and strips the closing / in output HTML. But the source must always include it.
templ automatically minifies HTML output.
Control Flow
Bare Go keywords — no special syntax required.
if/else
if user.IsAdmin {
<span class="badge">Admin</span>
} else if user.IsMod {
<span class="badge">Mod</span>
} else {
<span>User</span>
}switch
switch user.Role {
case "admin":
<span>Admin</span>
case "mod":
<span>Moderator</span>
default:
<span>User</span>
}for loops
for i, item := range items {
<li>{ fmt.Sprintf("%d", i) }: { item.Name }</li>
}Text Starting with Keywords
Text that starts with if, for, or switch triggers the parser. Two solutions:
// Wrap as expression
<p>{ "if you need this text" }</p>
<p>{ "for a rainy day" }</p>
// Capitalize the keyword
<p>If you need this text</p>
<p>For a rainy day</p>If the parser finds these keywords without an opening {, it returns an error.
Raw Go {{ }}
Scoped Go statements inside templ blocks. Use for intermediate variables:
{{ first := items[0] }}
{{ total := calculateTotal(items) }}
<p>{ first.Name } - total: { fmt.Sprintf("%d", total) }</p>Avoid calling expensive functions twice — cache in a variable with {{ }}.
Comments
Inside templ blocks — use HTML comments (rendered to output):
templ example() {
<!-- This appears in the HTML output -->
<!--
Multiline HTML comment.
-->
}Outside templ blocks — use Go comments (not rendered):
package main
// Standard Go comment — not in output
var greeting = "Hello!"
templ hello(name string) {
<p>{ name }</p>
}Nested HTML comments are not supported.