htmx-go-templates
htmx on Go html/template: how a handler picks page, fragment or redirect, how templates carry hx-* attributes, where client JS lives, and how errors and polling surface. TRIGGER — load BEFORE writing, not after: a .tmpl or .gohtml file is created or edited; an hx-* attribute or HX-* header is added or changed; a Go handler calls ExecuteTemplate or reads HX-Request; the prompt names htmx, hx-get, hx-swap, a fragment, partial, swap target, polling or an htmx event. Trigger on plain requests in a Go + htmx repo — "add a page", "make this form inline", "refresh this every 5s", "why doesn't the error show". Load alongside go-idiomatic. SKIP for templ components, JSON APIs and React or Solid UIs.
- License 许可
- MIT
- 最近更新
- 2026-11-11
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- htmx-go-templates
- description
- htmx on Go html/template: how a handler picks page, fragment or redirect, how templates carry hx-* attributes, where client JS lives, and how errors and polling surface. TRIGGER — load BEFORE writing, not after: a .tmpl or .gohtml file is created or edited; an hx-* attribute or HX-* header is added or changed; a Go handler calls ExecuteTemplate or reads HX-Request; the prompt names htmx, hx-get, hx-swap, a fragment, partial, swap target, polling or an htmx event. Trigger on plain requests in a Go + htmx repo — "add a page", "make this form inline", "refresh this every 5s", "why doesn't the error show". Load alongside go-idiomatic. SKIP for templ components, JSON APIs and React or Solid UIs.
- allowed-tools
- Read, Edit, Write, Glob, Grep, Bash(go:*), Bash(git:*)
02 资源文件 7
- 参考文档evals/evals.json8.7 KB
- 其他evals/fixtures/board.tmpl817 B
- 其他evals/fixtures/layout.tmpl1010 B
- 脚本evals/fixtures/server.go1.6 KB
- 参考文档references/client.md6.6 KB
- 参考文档references/responses.md5.2 KB
- 参考文档references/templates.md4.6 KB
03 SKILL.md 正文
htmx on Go html/template
The server owns state; a template only presents it; htmx moves HTML between the two. Most drift in these apps comes from that split being decided ad hoc, per handler and per template: seven ways to detect an htmx request, the same four attributes hand-copied across files, errors that vanish. The rules below make each of those decisions once.
This skill holds the general rules. The repo's AGENTS.md holds what only the repo knows: its swap-target ids, the names of its shared partials, and the htmx version it vendors. Read it first.
Where to look
| If you're working on… | Load |
|---|---|
A handler: choosing page / fragment / redirect, htmx headers, Vary, 303, error responses |
references/responses.md |
| A template: partials, repeated attribute bundles, forms, live regions, conditionals | references/templates.md |
Any client JS: whether you need it at all, islands, htmx glue, polling guards, aria-busy |
references/client.md |
The rules
- htmx headers go through one helper set, never hand-typed strings.
A typo in
"HX-Request"or a check for""instead of"true"fails silently; one package of constants and predicates makes the wrong spelling unrepresentable. If the repo has no such set yet, introduce it the first time a handler you touch needs an htmx header —Varyincluded. - Every route declares its response shape — page, fragment or redirect — in one place.
A handler that sniffs headers inline grows a new branch per feature. When one URL can return
either a page or a fragment, set
Vary: HX-Request, or a cache or the back button can serve the bare fragment as a page. A history-restore request (HX-History-Restore-Request: true) always gets the full page. - A non-htmx form POST answers 303 to a GET; an htmx one answers
HX-Locationor a fragment. 303 stops a refresh from resubmitting. A 303 to an htmx request is followed by the XHR and the whole redirected page gets swapped into the target, which is never what you want. - Forms work without JavaScript. Real
actionandmethod, a real submit button; htmx attributes layer on top. The non-JS path is also the path the 303 rule serves. - A repeated attribute bundle becomes a named
{{define}}partial. Four attributes copied to seventeen links drift the moment one copy changes. One partial, one place to change it. (codebase-designhas the vocabulary for why: one deep module beats many shallow copies.) - Polling never fights the user.
hx-syncon every polling element, a guard that skips the poll while the user is focused or typing inside the target, and a target that never contains an input the user may be editing. - Errors reach the user. htmx 2.x does not swap 4xx/5xx bodies by default, so a refused
action looks like nothing happened. Fix it in two halves: a
responseHandlingentry that swaps the status you use (typically 422), andHX-Retarget/HX-Reswapfrom the server so the error lands in a known region. That region is a persistentaria-livecontainer. - In-flight state is visible.
hx-disabled-elton submit controls so a double-click cannot double-submit;hx-indicatororaria-busyon the region being replaced. - Client JS is the exception, and it is never inline. Before writing any, ask whether the
server can own it (a preference, a re-rendered region) or HTML can (
<details>,<dialog>,popover). A widget with real client state becomes an island in the repo's frontend framework, mounted throughhtmx.onLoadand unmounted on swap. The only other JS is a fixed set of htmx glue listeners. No<script>blocks,onclickorhx-onin templates: inline handlers are untestable and re-run unpredictably on swaps. htmx config goes in<meta name="htmx-config">with"allowEval": false, so htmx itself refuseshx-onandjs:values. - Templates branch on presentation only.
{{if eq .Status "failed"}}chains are domain logic in the wrong layer. Compute a view-model field in Go (.Badge,.CanRetry) and let the template read it.
Choosing a response shape
request ──► HX-History-Restore-Request: true? ──yes──► page
│ no
▼
HX-Request: true? ──no──► POST? ──yes──► 303 to GET (or re-render page with 422 on invalid)
│ yes └─no──► page
▼
route declares: fragment ──► ExecuteTemplate(w, "<block>", vm)
page+select ──► page (template uses hx-select)
redirect ──► HX-Location (or HX-Redirect for a full reload)
references/responses.md has the helper set and a handler written both ways.
htmx version
These rules target htmx 2.x. Check the version the repo vendors before relying on defaults. If it is on 4.x, re-check rules 7 and 9 against the 4.x migration notes:
- error responses swap by default, with per-status control through
hx-status; - attribute inheritance is explicit, so bundles that relied on inheriting
hx-targetbreak; - event names changed, so listeners in the TS module need renaming.
What not to do
- Don't reach for the
response-targetsextension to route errors. It moves a server decision into markup and adds a second mechanism next toHX-Retarget. - Don't swap an
aria-liveregion withouterHTML. Screen readers track the original node; a replaced live region announces nothing. Swap itsinnerHTML. - Don't render into the
ResponseWriterdirectly. Execute into a buffer first, so a template error becomes a 500 rather than a half-written 200. - Don't add
hx-boostto fix a navigation problem. Declare the nav-link partial instead; boost changes every link and form under it at once. - Don't restate the htmx attribute reference here or in the repo; link htmx.org/reference.
04 同仓库更多 Skills
to-tickets
Breaks a source into independently-grabbable GitHub tickets. For a code feature the source is the plan (`docs/plans/<feature>.md`, from /to-plan) and every new or changed symbol lands in exactly one ticket, its signatures copied into the ticket body; a non-code feature is cut from the PRD's slices. Cuts tickets for one feature per run. If a code feature has no approved plan, offers /to-plan instead of cutting from prose. In a repo that runs command-centre, also emits `[[task]]` blocks into its config; in a plain treepad repo, emits a Batch Manifest instead so the tickets materialise as stacked worktrees.
to-seams
Records an agreed-but-unlanded cross-repo interface as a seam: the file a consumer's agent gets pasted into its prompt as ground truth, plus the `[[seam]]` block command-centre reads for the retirement pointer. Use when two repos need to build against an interface neither has landed yet — "record a seam for X", "we agreed on this schema, seam it", "add a seam so Y can build against Z's new field". Halts for approval before writing either file.
to-roadmap
Prioritises settled features into a Now / Next / Later kanban board, rendered as a self-contained ./ideas/roadmap.html. Each card is a feature, not a ticket — one card later becomes several tickets via /to-prd -> /to-plan -> /to-tickets. Boards the features in ./docs/approach.md, or the "## Accepted ideas" in ./ideas/CONTEXT.md when there is no approach doc. The approach doc is settled truth: this assigns tiers and reasons about ordering, it never re-litigates what was decided.
to-prd
Turns one feature idea into a PRD built on journeys: a one-line problem, journeys (each ending in an observable state) and delivery slices that say what is usable after each. Interviews, explores the codebase, and saves to docs/prds/<feature>.md. Covers exactly one feature: given a multi-feature source, ask which one. Reads any approach doc or ideate report as priors so it doesn't re-ask what's settled, but needs none. Starts the per-feature spine (/to-prd -> /to-plan -> /to-tickets).
to-plan
Turns one feature's PRD into a skimmable plan in docs/plans/<feature>.md: a short overview, then per phase a glossary, the journeys it satisfies, and its new and modified definitions (types, enums, ports, schema), with the project:<slug> label. No code, branch or PR; the user's review of the file is the gate. Use for "plan this feature", "write the contract", "what are the types". One feature per run. Slash-only.
ticket-tracker
Use when the request is about the *current state* of GitHub issues rather than creating new ones: "where are my tickets", "show the board", "what's in progress", "what's ready to pick up", "move issue N to in-review", "mark N done", "update the ticket status". Live triage board over GitHub issues via the gh CLI, moving each between backlog -> ready -> in-progress -> in-review -> done using status labels. This is the ticket-level board; ./ideas/roadmap.html is the feature-level one. To file new tickets, use /to-tickets, file-issue for a single ad-hoc one, or /sweep for a batch of small fixes.