--- name: templ-htmx description: Build interactive hypermedia-driven applications with templ and HTMX. Use when creating dynamic UIs, real-time updates, AJAX interactions, mentions 'HTMX', 'dynamic content', or 'interactive templ app'. --- # Templ + HTMX Integration ## Overview HTMX enables modern, interactive web applications with minimal JavaScript. Combined with templ's type-safe components, you get fast, reliable hypermedia-driven UIs. **Key Benefits:** - No JavaScript framework needed - Server-side rendering - Minimal client-side code - Progressive enhancement - Type-safe components ## When to Use This Skill Use when: - Building interactive UIs - Creating dynamic content - User mentions "HTMX", "dynamic updates", "real-time" - Implementing AJAX-like behavior without JS - Building SPAs without frameworks ## Quick Start ### 1. Import and Mount HTMX First, import the htmx package and mount it in your server: ```go import ( "within.website/x/htmx" ) func main() { mux := http.NewServeMux() // Mount HTMX static files at /.within.website/x/htmx/ htmx.Mount(mux) // ... other routes } ``` ### 2. Add HTMX to Layout ```templ package components import "within.website/x/htmx" templ Layout(title string) { { title } @htmx.Use() { children... } } ``` The `htmx.Use()` component includes the core HTMX library. You can add extensions: ```templ // Add SSE and path-params extensions @htmx.Use("sse", "path-params") ``` Available extensions: `event-header`, `path-params`, `remove-me`, `websocket`. ### 3. Detect HTMX Requests Use the `htmx.Is()` function to check if a request was made by HTMX: ```go import "within.website/x/htmx" func handler(w http.ResponseWriter, r *http.Request) { if htmx.Is(r) { // Return partial HTML fragment for HTMX components.Partial().Render(r.Context(), w) } else { // Return full page for direct navigation components.FullPage().Render(r.Context(), w) } } ``` ### 4. Create Interactive Component ```templ templ Counter(count int) {

Count: { strconv.Itoa(count) }

} ``` ### 5. Create Handler ```go func incrementHandler(w http.ResponseWriter, r *http.Request) { count := getCount() + 1 saveCount(count) components.Counter(count).Render(r.Context(), w) } ``` ## Core HTMX Attributes ### hx-get / hx-post Trigger HTTP requests: ```templ templ SearchBox() {
} ``` Handler: ```go func searchHandler(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") results := search(query) components.SearchResults(results).Render(r.Context(), w) } ``` ### hx-target Specify where to insert response: ```templ templ LoadMore(page int) { } ``` ### hx-swap Control how content is swapped: ```templ // innerHTML (default) hx-swap="innerHTML" // outerHTML - replace element itself hx-swap="outerHTML" // beforeend - append inside hx-swap="beforeend" // afterend - insert after hx-swap="afterend" ``` ### hx-trigger Control when requests fire: ```templ // On click (default for buttons) // On change // On page load
// Every 5 seconds
``` ## Common Patterns ### Pattern 1: Live Search Component: ```templ templ SearchBox() {
Searching...
} templ SearchResults(results []string) { } ``` Handler: ```go func searchHandler(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") results := performSearch(query) components.SearchResults(results).Render(r.Context(), w) } ``` ### Pattern 2: Infinite Scroll ```templ templ PostList(posts []Post, page int) {
for _, post := range posts { @PostCard(post) }
if len(posts) > 0 {
Loading more...
} } ``` ### Pattern 3: Delete with Confirmation ```templ templ DeleteButton(itemID string) { } ``` Handler: ```go func deleteHandler(w http.ResponseWriter, r *http.Request) { itemID := strings.TrimPrefix(r.URL.Path, "/items/") deleteItem(itemID) // Return empty to remove element w.WriteHeader(http.StatusOK) } ``` ### Pattern 4: Inline Edit ```templ templ EditableField(id string, value string) {
{ value }
} templ EditForm(id string, value string) {
} ``` ### Pattern 5: Form Validation ```templ templ SignupForm() {
} templ ValidationError(message string) { { message } } ``` ### Pattern 6: Polling / Real-time Updates ```templ templ LiveStats() {
Loading stats...
} templ StatsDisplay(stats Stats) {

Users online: { strconv.Itoa(stats.UsersOnline) }

Active sessions: { strconv.Itoa(stats.Sessions) }

} ``` ## Advanced Patterns ### Out-of-Band Updates (OOB) Update multiple parts of page: ```templ templ CartButton(count int) { } templ AddToCartResponse(item Item) { // Main response
Added { item.Name } to cart!
// Update cart button (different part of page)
@CartButton(getCartCount())
} ``` ### Progressive Enhancement ```templ templ Form() {
} ``` Works without JavaScript, enhanced with HTMX. ### Loading States ```templ templ DataTable() {
Loading data...
} ``` CSS: ```css .htmx-indicator { display: none; } .htmx-request .htmx-indicator { display: inline; } .htmx-request.htmx-indicator { display: inline; } ``` ## Response Headers ### HX-Trigger Trigger client-side events: ```go func handler(w http.ResponseWriter, r *http.Request) { // Do work... // Trigger custom event w.Header().Set("HX-Trigger", "itemCreated") components.Success().Render(r.Context(), w) } ``` Client side: ```javascript document.body.addEventListener("itemCreated", function (evt) { console.log("Item created!"); }); ``` ### HX-Redirect ```go func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("HX-Redirect", "/dashboard") w.WriteHeader(http.StatusOK) } ``` ### HX-Refresh ```go func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("HX-Refresh", "true") w.WriteHeader(http.StatusOK) } ``` ### Special Status Code Stop polling with status code 286: ```go import "within.website/x/htmx" func pollHandler(w http.ResponseWriter, r *http.Request) { if shouldStopPolling() { w.WriteHeader(htmx.StatusStopPolling) return } // ... return normal content } ``` ### Request Headers Access HTMX request headers: ```go import "within.website/x/htmx" func handler(w http.ResponseWriter, r *http.Request) { // Check if this is an HTMX request if htmx.Is(r) { // Get user's response to hx-prompt promptResponse := r.Header.Get(htmx.HeaderPrompt) } } ``` ## Best Practices 1. **Keep handlers focused** - Return only the HTML fragment needed 2. **Use semantic HTML** - Works without JS 3. **Handle errors gracefully** - Return error components 4. **Optimize responses** - Send minimal HTML 5. **Use OOB for multi-updates** - Update multiple page sections 6. **Progressive enhancement** - Always provide fallback ## Full Example: Todo App ```templ // components/todo.templ package components type Todo struct { ID string Text string Completed bool } templ TodoApp(todos []Todo) { @Layout("Todo App") {

My Todos

@TodoForm() @TodoList(todos)
} } templ TodoForm() {
} templ TodoList(todos []Todo) { } templ TodoItem(todo Todo) {
  • { todo.Text }
  • } ``` Handlers: ```go func todosHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": todos := getAllTodos() components.TodoApp(todos).Render(r.Context(), w) case "POST": r.ParseForm() todo := createTodo(r.FormValue("text")) components.TodoItem(todo).Render(r.Context(), w) } } func todoToggleHandler(w http.ResponseWriter, r *http.Request) { id := extractID(r.URL.Path) todo := toggleTodo(id) components.TodoItem(todo).Render(r.Context(), w) } func todoDeleteHandler(w http.ResponseWriter, r *http.Request) { id := extractID(r.URL.Path) deleteTodo(id) w.WriteHeader(http.StatusOK) // Empty response removes element } ``` ## Resources - [HTMX Documentation](https://htmx.org/docs/) - [HTMX Examples](https://htmx.org/examples/) - [Hypermedia Systems Book](https://hypermedia.systems/) ## Next Steps - **Style components** → Use `templ-css` skill - **Deploy** → Use `templ-deployment` skill - **Test** → Use `templ-testing` skill