WHEN: User is building Go/Templ web apps, using templUI components, converting sites to Templ, or asking about templ syntax, Script() templates, HTMX/Alpine integration, or JavaScript in templ WHEN NOT: Non-Go projects, general web development without templ
Applies templUI patterns and HTMX/Alpine.js best practices for building interactive Go/Templ web applications.
/plugin marketplace add gopherguides/gopher-ai/plugin install go-web@gopher-aiThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Apply templUI patterns and HTMX/Alpine.js best practices when building Go/Templ web applications.
The Go/Templ stack uses three complementary tools for interactivity:
| Tool | Purpose | Use For |
|---|---|---|
| HTMX | Server-driven interactions | AJAX requests, form submissions, partial page updates, live search |
| Alpine.js | Client-side state & reactivity | Toggles, animations, client-side filtering, transitions, local state |
| templUI | Pre-built UI components | Dropdowns, dialogs, tabs, sidebars (uses vanilla JS via Script() templates) |
Note: templUI components use vanilla JavaScript (not Alpine.js) via Script() templates. This is fine - Alpine.js is still part of the stack for your custom client-side needs.
HTMX and Alpine.js work great together. Use HTMX for server communication, Alpine for client-side enhancements.
<!-- HTMX: Server-driven (fetches HTML from server) -->
<button hx-get="/api/users" hx-target="#user-list">Load Users</button>
<!-- Alpine: Client-side state (no server call) -->
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>
<!-- Combined: HTMX loads data, Alpine filters it -->
<div x-data="{ filter: '' }">
<input x-model="filter" placeholder="Filter...">
<div hx-get="/users" hx-trigger="load">
<template x-for="user in users.filter(u => u.name.includes(filter))">
<div x-text="user.name"></div>
</template>
</div>
</div>
Alpine-Morph Extension: Preserves Alpine state across HTMX swaps:
<script src="https://unpkg.com/htmx.org/dist/ext/alpine-morph.js"></script>
<div hx-ext="alpine-morph" hx-swap="morph">...</div>
htmx.process() for Alpine Conditionals: When Alpine's x-if renders HTMX content:
<template x-if="showForm">
<form hx-post="/submit" x-init="htmx.process($el)">...</form>
</template>
Triggering HTMX from Alpine:
<button @click="htmx.trigger($refs.form, 'submit')">Submit</button>
templUI components handle their own interactivity via Script() templates using vanilla JavaScript and Floating UI for positioning.
Go expressions { value } do NOT interpolate inside <script> tags or inline event handlers. They are treated as literal text, causing errors like:
GET http://localhost:8008/app/quotes/%7B%20id.String()%20%7D 400 (Bad Request)
The %7B and %7D are URL-encoded { and } - proof the expression wasn't evaluated.
Use data-* attributes to pass Go values, then access via JavaScript:
<button
data-quote-id={ quote.ID.String() }
onclick="openPublishModal(this.dataset.quoteId)">
Publish
</button>
For multiple values:
<div
data-id={ item.ID.String() }
data-name={ item.Name }
data-status={ item.Status }
onclick="handleClick(this.dataset)">
Automatically JSON-encodes arguments and prevents XSS:
<button onclick={ templ.JSFuncCall("openPublishModal", quote.ID.String()) }>
Publish
</button>
With multiple arguments:
<button onclick={ templ.JSFuncCall("updateItem", item.ID.String(), item.Name, item.Active) }>
To pass the event object, use templ.JSExpression:
<button onclick={ templ.JSFuncCall("handleClick", templ.JSExpression("event"), quote.ID.String()) }>
Inside <script> tags, use {{ value }} (double braces) for interpolation:
<script>
const quoteId = "{{ quote.ID.String() }}";
const itemName = "{{ item.Name }}";
openPublishModal(quoteId);
</script>
Outside strings (bare expressions), values are JSON-encoded:
<script>
const config = {{ templ.JSONString(config) }};
const isActive = {{ item.Active }}; // outputs: true or false
</script>
Pass complex structs/maps to JavaScript via attributes:
<div data-config={ templ.JSONString(config) }>
<script>
const el = document.querySelector('[data-config]');
const config = JSON.parse(el.dataset.config);
</script>
Or use templ.JSONScript:
@templ.JSONScript("config-data", config)
<script>
const config = JSON.parse(document.getElementById('config-data').textContent);
</script>
Ensures scripts are only rendered once, even when component is used multiple times:
var publishHandle = templ.NewOnceHandle()
templ QuoteRow(quote Quote) {
@publishHandle.Once() {
<script>
function openPublishModal(id) {
fetch(`/api/quotes/${id}/publish`, { method: 'POST' });
}
</script>
}
<button
data-id={ quote.ID.String() }
onclick="openPublishModal(this.dataset.id)">
Publish
</button>
}
| Scenario | Use |
|---|---|
| Simple onclick with one value | Data attribute or templ.JSFuncCall |
| Multiple values needed in JS | Data attributes |
| Need event object | templ.JSFuncCall with templ.JSExpression("event") |
| Inline script with Go values | {{ value }} double braces |
| Complex object/struct | templ.JSONString or templ.JSONScript |
| Reusable script in loop | templ.OnceHandle |
// WRONG - won't interpolate, becomes literal text
onclick="doThing({ id })"
// WRONG - single braces don't work in scripts
<script>const x = { value };</script>
// WRONG - Go expression in URL string inside script
<script>
fetch(`/api/quotes/{ id }/publish`) // BROKEN
</script>
// CORRECT alternatives:
onclick={ templ.JSFuncCall("doThing", id) }
<script>const x = "{{ value }}";</script>
<button data-id={ id } onclick="doFetch(this.dataset.id)">
Install CLI:
go install github.com/templui/templui/cmd/templui@latest
Key Commands:
templui init # Initialize project, creates .templui.json
templui add button card # Add specific components
templui add "*" # Add ALL components
templui add -f dropdown # Force update existing component
templui list # List available components
templui new my-app # Create new project
templui upgrade # Update CLI to latest version
ALWAYS use the CLI to add/update components - it fetches the complete component including Script() templates that may be missing if copied manually.
Components with JavaScript include a Script() template function. You MUST add these to your base layout's <head>:
// In your base layout <head>:
@popover.Script() // Required for: popover, dropdown, tooltip, combobox
@dropdown.Script() // Required for: dropdown
@dialog.Script() // Required for: dialog, sheet, alertdialog
@accordion.Script() // Required for: accordion, collapsible
@tabs.Script() // Required for: tabs
@carousel.Script() // Required for: carousel
@toast.Script() // Required for: toast/sonner
@clipboard.Script() // Required for: copybutton
Component Dependencies:
| Component | Requires Script() from |
|---|---|
| dropdown | dropdown, popover |
| tooltip | popover |
| combobox | popover |
| sheet | dialog |
| alertdialog | dialog |
| collapsible | accordion |
If a component doesn't work (no click events, no positioning), check that:
When converting HTML/React/Vue to Go/Templ:
Conversion Process:
class stays as class in templclassName (React) → class{ variable } or @component()Templ Syntax Quick Reference:
package components
type ButtonProps struct {
Text string
Variant string
}
templ Button(props ButtonProps) {
<button class={ "btn", props.Variant }>
{ props.Text }
</button>
}
// Conditional
if condition {
<span>Shown</span>
}
// Loops
for _, item := range items {
<li>{ item.Name }</li>
}
// Composition
@Header()
@Content() {
// Children
}
Audit Checklist:
templui add or manually copied?Common Issues to Check:
@popover.Script() → dropdowns/tooltips don't open@dialog.Script() → dialogs/sheets don't workimport "github.com/templui/templui/components/button"
import "github.com/templui/templui/components/dropdown"
import "github.com/templui/templui/components/dialog"
JavaScript URL contains literal { or %7B (URL-encoded brace):
Go expressions don't interpolate in <script> tags. Use data attributes:
// WRONG: <script>fetch(`/api/{ id }`)</script>
// RIGHT:
<button data-id={ id } onclick="doFetch(this.dataset.id)">
See "CRITICAL: Templ Interpolation in JavaScript" section above.
Component not responding to clicks:
@dropdown.Script(), @popover.Script()templui add -f dropdown popoverDropdown/Tooltip not positioning correctly:
@popover.Script() is in layout (uses Floating UI)templui add -f popoverDialog/Sheet not opening:
@dialog.Script() to layouttemplui add -f dialogtemplUI:
HTMX + Alpine.js:
Templ:
This skill provides templUI and HTMX/Alpine.js best practices for Go/Templ web development.
<!-- cache:end -->Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.