templui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<!-- cache:start -->
<!-- cache:start -->

templUI & HTMX/Alpine Best Practices

templUI & HTMX/Alpine 最佳实践

Apply templUI patterns and HTMX/Alpine.js best practices when building Go/Templ web applications.
在构建Go/Templ Web应用时,遵循templUI模式与HTMX/Alpine.js最佳实践。

The Frontend Stack

前端技术栈

The Go/Templ stack uses three complementary tools for interactivity:
ToolPurposeUse For
HTMXServer-driven interactionsAJAX requests, form submissions, partial page updates, live search
Alpine.jsClient-side state & reactivityToggles, animations, client-side filtering, transitions, local state
templUIPre-built UI componentsDropdowns, 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.

Go/Templ技术栈使用三个互补工具实现交互性:
工具用途适用场景
HTMX服务器驱动交互AJAX请求、表单提交、页面局部更新、实时搜索
Alpine.js客户端状态与响应式处理开关控制、动画、客户端筛选、过渡效果、本地状态管理
templUI预构建UI组件下拉菜单、对话框、标签页、侧边栏(通过Script()模板使用原生JavaScript)
注意: templUI组件通过Script()模板使用原生JavaScript(而非Alpine.js)。这是正常的——Alpine.js仍可用于满足你的自定义客户端需求,属于技术栈的一部分。

HTMX + Alpine.js Integration

HTMX + Alpine.js 集成

HTMX and Alpine.js work great together. Use HTMX for server communication, Alpine for client-side enhancements.
HTMX与Alpine.js可以完美协作。使用HTMX处理服务器通信,使用Alpine.js实现客户端增强。

When to Use Each

适用场景区分

html
<!-- 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>
html
<!-- HTMX:服务器驱动交互(从服务器获取HTML) -->
<button hx-get="/api/users" hx-target="#user-list">加载用户</button>

<!-- Alpine:客户端状态管理(无需服务器调用) -->
<div x-data="{ open: false }">
  <button @click="open = !open">切换</button>
  <div x-show="open">内容</div>
</div>

<!-- 结合使用:HTMX加载数据,Alpine进行筛选 -->
<div x-data="{ filter: '' }">
  <input x-model="filter" placeholder="筛选...">
  <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>

Key Integration Patterns

核心集成模式

Alpine-Morph Extension: Preserves Alpine state across HTMX swaps:
html
<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:
html
<template x-if="showForm">
  <form hx-post="/submit" x-init="htmx.process($el)">...</form>
</template>
Triggering HTMX from Alpine:
html
<button @click="htmx.trigger($refs.form, 'submit')">Submit</button>

Alpine-Morph扩展:在HTMX内容替换时保留Alpine状态:
html
<script src="https://unpkg.com/htmx.org/dist/ext/alpine-morph.js"></script>
<div hx-ext="alpine-morph" hx-swap="morph">...</div>
htmx.process()处理Alpine条件渲染:当Alpine的
x-if
渲染HTMX内容时:
html
<template x-if="showForm">
  <form hx-post="/submit" x-init="htmx.process($el)">...</form>
</template>
从Alpine触发HTMX操作
html
<button @click="htmx.trigger($refs.form, 'submit')">提交</button>

templUI Components (Vanilla JS)

templUI 组件(原生JavaScript)

templUI components handle their own interactivity via Script() templates using vanilla JavaScript and Floating UI for positioning.

templUI组件通过Script()模板使用原生JavaScript和Floating UI进行定位,自行处理交互逻辑。

CRITICAL: Templ Interpolation in JavaScript

重点注意:Templ中的JavaScript插值

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.
Go表达式
{ value }
<script>
标签或内联事件处理程序中不会被插值
,它们会被当作字面文本处理,导致如下错误:
GET http://localhost:8008/app/quotes/%7B%20id.String()%20%7D 400 (Bad Request)
其中
%7B
%7D
是URL编码后的
{
}
——这证明表达式未被解析。

Pattern 1: Data Attributes (Recommended)

模式1:数据属性(推荐)

Use
data-*
attributes to pass Go values, then access via JavaScript:
templ
<button
  data-quote-id={ quote.ID.String() }
  onclick="openPublishModal(this.dataset.quoteId)">
  Publish
</button>
For multiple values:
templ
<div
  data-id={ item.ID.String() }
  data-name={ item.Name }
  data-status={ item.Status }
  onclick="handleClick(this.dataset)">
使用
data-*
属性传递Go值,然后通过JavaScript访问:
templ
<button
  data-quote-id={ quote.ID.String() }
  onclick="openPublishModal(this.dataset.quoteId)">
  发布
</button>
传递多个值:
templ
<div
  data-id={ item.ID.String() }
  data-name={ item.Name }
  data-status={ item.Status }
  onclick="handleClick(this.dataset)">

Pattern 2: templ.JSFuncCall (for onclick handlers)

模式2:templ.JSFuncCall(用于点击事件处理)

Automatically JSON-encodes arguments and prevents XSS:
templ
<button onclick={ templ.JSFuncCall("openPublishModal", quote.ID.String()) }>
  Publish
</button>
With multiple arguments:
templ
<button onclick={ templ.JSFuncCall("updateItem", item.ID.String(), item.Name, item.Active) }>
To pass the event object, use
templ.JSExpression
:
templ
<button onclick={ templ.JSFuncCall("handleClick", templ.JSExpression("event"), quote.ID.String()) }>
自动对参数进行JSON编码并防止XSS攻击:
templ
<button onclick={ templ.JSFuncCall("openPublishModal", quote.ID.String()) }>
  发布
</button>
传递多个参数:
templ
<button onclick={ templ.JSFuncCall("updateItem", item.ID.String(), item.Name, item.Active) }>
需要传递事件对象时,使用
templ.JSExpression
templ
<button onclick={ templ.JSFuncCall("handleClick", templ.JSExpression("event"), quote.ID.String()) }>

Pattern 3: Double-Braces Inside Script Strings

模式3:脚本字符串中的双括号

Inside
<script>
tags, use
{{ value }}
(double braces) for interpolation:
templ
<script>
  const quoteId = "{{ quote.ID.String() }}";
  const itemName = "{{ item.Name }}";
  openPublishModal(quoteId);
</script>
Outside strings (bare expressions), values are JSON-encoded:
templ
<script>
  const config = {{ templ.JSONString(config) }};
  const isActive = {{ item.Active }};  // outputs: true or false
</script>
<script>
标签内,使用
{{ value }}
(双括号)进行插值:
templ
<script>
  const quoteId = "{{ quote.ID.String() }}";
  const itemName = "{{ item.Name }}";
  openPublishModal(quoteId);
</script>
在字符串外(裸表达式),值会被JSON编码:
templ
<script>
  const config = {{ templ.JSONString(config) }};
  const isActive = {{ item.Active }};  // 输出:true 或 false
</script>

Pattern 4: templ.JSONString for Complex Data

模式4:templ.JSONString传递复杂数据

Pass complex structs/maps to JavaScript via attributes:
templ
<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
@templ.JSONScript("config-data", config)

<script>
  const config = JSON.parse(document.getElementById('config-data').textContent);
</script>
通过属性将复杂结构体/映射传递给JavaScript:
templ
<div data-config={ templ.JSONString(config) }>

<script>
  const el = document.querySelector('[data-config]');
  const config = JSON.parse(el.dataset.config);
</script>
或者使用
templ.JSONScript
templ
@templ.JSONScript("config-data", config)

<script>
  const config = JSON.parse(document.getElementById('config-data').textContent);
</script>

Pattern 5: templ.OnceHandle for Reusable Scripts

模式5:templ.OnceHandle复用脚本

Ensures scripts are only rendered once, even when component is used multiple times:
templ
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>
}
确保脚本仅渲染一次,即使组件被多次使用:
templ
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)">
    发布
  </button>
}

When to Use Each Pattern

各模式适用场景

ScenarioUse
Simple onclick with one valueData attribute or
templ.JSFuncCall
Multiple values needed in JSData 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
场景推荐使用
带单个值的简单点击事件数据属性或
templ.JSFuncCall
JavaScript中需要多个值数据属性
需要传递事件对象结合
templ.JSExpression("event")
templ.JSFuncCall
内联脚本中使用Go值
{{ value }}
双括号
复杂对象/结构体
templ.JSONString
templ.JSONScript
循环中复用脚本
templ.OnceHandle

Common Mistakes

常见错误

templ
// 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)">

templ
// 错误写法 - 不会插值,会变成字面文本
onclick="doThing({ id })"

// 错误写法 - 单括号在脚本中无效
<script>const x = { value };</script>

// 错误写法 - 脚本中的URL字符串包含Go表达式
<script>
  fetch(`/api/quotes/{ id }/publish`)  // 失效
</script>

// 正确写法:
onclick={ templ.JSFuncCall("doThing", id) }

<script>const x = "{{ value }}";</script>

<button data-id={ id } onclick="doFetch(this.dataset.id)">

templUI CLI Tool

templUI CLI工具

Install CLI:
bash
go install github.com/templui/templui/cmd/templui@latest
Key Commands:
bash
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.

安装CLI:
bash
go install github.com/templui/templui/cmd/templui@latest
核心命令:
bash
templui init                    # 初始化项目,创建.templui.json文件
templui add button card         # 添加指定组件
templui add "*"                 # 添加所有组件
templui add -f dropdown         # 强制更新现有组件
templui list                    # 列出可用组件
templui new my-app              # 创建新项目
templui upgrade                 # 将CLI更新到最新版本
务必使用CLI添加/更新组件——它会获取完整的组件,包括手动复制可能缺失的Script()模板。

Script() Templates - REQUIRED for Interactive Components

Script()模板 - 交互式组件必备

Components with JavaScript include a
Script()
template function. You MUST add these to your base layout's
<head>
:
templ
// 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:
ComponentRequires Script() from
dropdowndropdown, popover
tooltippopover
comboboxpopover
sheetdialog
alertdialogdialog
collapsibleaccordion
If a component doesn't work (no click events, no positioning), check that:
  1. The Script() template is called in the layout
  2. The component was installed via CLI (not manually copied)
  3. All dependency scripts are included

包含JavaScript的组件会有一个
Script()
模板函数。必须将这些函数添加到基础布局的
<head>
中:
templ
// 在你的基础布局<head>中:
@popover.Script()      // 以下组件需要:弹出框、下拉菜单、提示框、组合框
@dropdown.Script()     // 下拉菜单需要
@dialog.Script()       // 以下组件需要:对话框、侧边面板、警告对话框
@accordion.Script()    // 以下组件需要:折叠面板、可折叠区域
@tabs.Script()         // 标签页需要
@carousel.Script()     // 轮播图需要
@toast.Script()        // 以下组件需要:提示消息/Sonner
@clipboard.Script()    // 复制按钮需要
组件依赖关系:
组件依赖的Script()
dropdowndropdown, popover
tooltippopover
comboboxpopover
sheetdialog
alertdialogdialog
collapsibleaccordion
如果组件无法正常工作(无点击事件、定位错误),请检查:
  1. 基础布局中是否调用了所需的Script()
  2. 组件是否通过CLI安装(而非手动复制)
  3. 是否包含了所有依赖脚本

Converting Sites to Templ/templUI

将网站迁移到Templ/templUI

When converting HTML/React/Vue to Go/Templ:
Conversion Process:
  1. Analyze existing UI patterns
  2. Map to templUI base components
  3. Convert syntax:
    • class
      stays as
      class
      in templ
    • className
      (React) →
      class
    • React/Vue event handlers → vanilla JS via Script() or HTMX
    • Dynamic content → templ expressions
      { variable }
      or
      @component()
  4. Add required Script() templates to layout
  5. Set up proper Go package structure
Templ Syntax Quick Reference:
templ
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
}

将HTML/React/Vue项目迁移到Go/Templ时:
迁移流程:
  1. 分析现有UI模式
  2. 映射到templUI基础组件
  3. 转换语法:
    • class
      在templ中保持为
      class
    • React的
      className
      class
    • React/Vue事件处理程序 → 通过Script()或HTMX使用原生JavaScript
    • 动态内容 → templ表达式
      { variable }
      @component()
  4. 将所需的Script()模板添加到布局中
  5. 搭建正确的Go包结构
Templ语法快速参考:
templ
package components

type ButtonProps struct {
    Text    string
    Variant string
}

templ Button(props ButtonProps) {
    <button class={ "btn", props.Variant }>
        { props.Text }
    </button>
}

// 条件渲染
if condition {
    <span>显示内容</span>
}

// 循环渲染
for _, item := range items {
    <li>{ item.Name }</li>
}

// 组件组合
@Header()
@Content() {
    // 子内容
}

Auditing for Better Component Usage

组件使用优化审计

Audit Checklist:
  1. Script() Templates: Are all required Script() calls in the base layout?
  2. CLI Installation: Were components added via
    templui add
    or manually copied?
  3. Component Consistency: Same patterns using same components?
  4. Base Component Usage: Custom code that could use templUI?
  5. Dark Mode: Tailwind dark: variants used?
  6. Responsive: Mobile breakpoints applied?
Common Issues to Check:
  • Missing
    @popover.Script()
    → dropdowns/tooltips don't open
  • Missing
    @dialog.Script()
    → dialogs/sheets don't work
  • Manually copied components missing Script() template files

审计清单:
  1. Script()模板:基础布局中是否包含所有必需的Script()调用?
  2. CLI安装:组件是通过
    templui add
    添加还是手动复制的?
  3. 组件一致性:相同场景是否使用相同组件?
  4. 基础组件复用:自定义代码是否可以替换为templUI组件?
  5. 深色模式:是否使用了Tailwind的dark:变体?
  6. 响应式设计:是否适配了移动端断点?
常见问题检查:
  • 缺少
    @popover.Script()
    → 下拉菜单/提示框无法打开
  • 缺少
    @dialog.Script()
    → 对话框/侧边面板无法工作
  • 手动复制的组件缺失Script()模板文件

Import Pattern

导入模式

go
import "github.com/templui/templui/components/button"
import "github.com/templui/templui/components/dropdown"
import "github.com/templui/templui/components/dialog"

go
import "github.com/templui/templui/components/button"
import "github.com/templui/templui/components/dropdown"
import "github.com/templui/templui/components/dialog"

Troubleshooting

故障排查

JavaScript URL contains literal
{
or
%7B
(URL-encoded brace):
Go expressions don't interpolate in
<script>
tags. Use data attributes:
templ
// 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:
  1. Check Script() is in layout:
    @dropdown.Script()
    ,
    @popover.Script()
  2. Reinstall:
    templui add -f dropdown popover
  3. Check browser console for JS errors
Dropdown/Tooltip not positioning correctly:
  1. Ensure
    @popover.Script()
    is in layout (uses Floating UI)
  2. Reinstall popover:
    templui add -f popover
Dialog/Sheet not opening:
  1. Add
    @dialog.Script()
    to layout
  2. Reinstall:
    templui add -f dialog

JavaScript URL中包含字面量
{
%7B
(URL编码后的大括号):
Go表达式在
<script>
标签中不会被插值,请使用数据属性:
templ
// 错误写法:<script>fetch(`/api/{ id }`)</script>
// 正确写法:
<button data-id={ id } onclick="doFetch(this.dataset.id)">
请参考上方“重点注意:Templ中的JavaScript插值”章节。
组件无点击响应:
  1. 检查布局中是否包含Script():
    @dropdown.Script()
    @popover.Script()
  2. 重新安装组件:
    templui add -f dropdown popover
  3. 检查浏览器控制台的JavaScript错误
下拉菜单/提示框定位错误:
  1. 确保布局中包含
    @popover.Script()
    (依赖Floating UI)
  2. 重新安装弹出框组件:
    templui add -f popover
对话框/侧边面板无法打开:
  1. @dialog.Script()
    添加到布局中
  2. 重新安装组件:
    templui add -f dialog

Resources

参考资源

templUI:
HTMX + Alpine.js:
Templ:

This skill provides templUI and HTMX/Alpine.js best practices for Go/Templ web development.
<!-- cache:end -->
templUI:
HTMX + Alpine.js:
Templ:

本技能为Go/Templ Web开发提供templUI与HTMX/Alpine.js的最佳实践指导。
<!-- cache:end -->