The code and data behind xeiaso.net
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(skills/templ-htmx): document required xeiaso htmx workflow

Rewrite the skill into progressive disclosure and add focused resources that require htmx.Mount, @htmx.Use(), and htmx.Is for request branching.

Assisted-by: openai/gpt-5.3-codex via OpenCode
Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso 5af914fb e1df1002

+179 -612
+36 -612
.claude/skills/templ-htmx/SKILL.md
··· 5 5 6 6 # Templ + HTMX Integration 7 7 8 - ## Overview 8 + Use progressive disclosure: first make one interaction work, then scale to advanced behaviors. 9 9 10 - HTMX enables modern, interactive web applications with minimal JavaScript. Combined with templ's type-safe components, you get fast, reliable hypermedia-driven UIs. 10 + ## Level 1: First Working Flow 11 11 12 - **Key Benefits:** 12 + Use this skill for server-driven interactivity without a JS framework. 13 13 14 - - No JavaScript framework needed 15 - - Server-side rendering 16 - - Minimal client-side code 17 - - Progressive enhancement 18 - - Type-safe components 19 - 20 - ## When to Use This Skill 21 - 22 - Use when: 23 - 24 - - Building interactive UIs 25 - - Creating dynamic content 26 - - User mentions "HTMX", "dynamic updates", "real-time" 27 - - Implementing AJAX-like behavior without JS 28 - - Building SPAs without frameworks 29 - 30 - ## Quick Start 31 - 32 - ### 1. Import and Mount HTMX 33 - 34 - First, import the htmx package and mount it in your server: 14 + 1. Mount HTMX assets in server setup. 15 + 2. Include HTMX script in the layout. 16 + 3. Add `hx-*` attributes to a component. 17 + 4. Return a partial component from the handler. 18 + 5. Branch full-page vs fragment responses with HTMX request detection. 35 19 36 20 ```go 37 - import ( 38 - "xeiaso.net/v4/web/htmx" 39 - ) 21 + import "xeiaso.net/v4/web/htmx" 40 22 41 23 func main() { 42 24 mux := http.NewServeMux() 43 - 44 - // Mount HTMX static files at /.within.website/x/htmx/ 45 25 htmx.Mount(mux) 46 - 47 - // ... other routes 48 26 } 49 27 ``` 50 28 51 - ### 2. Add HTMX to Layout 52 - 53 29 ```templ 54 - package components 55 - 56 30 import "xeiaso.net/v4/web/htmx" 57 31 58 - templ Layout(title string) { 59 - <!DOCTYPE html> 32 + templ Layout() { 60 33 <html> 61 - <head> 62 - <title>{ title }</title> 63 - @htmx.Use() 64 - </head> 65 - <body> 66 - { children... } 67 - </body> 34 + <head>@htmx.Use()</head> 35 + <body>{ children... }</body> 68 36 </html> 69 37 } 70 38 ``` 71 39 72 - The `htmx.Use()` component includes the core HTMX library. You can add extensions: 73 - 74 - ```templ 75 - // Add SSE and path-params extensions 76 - @htmx.Use("sse", "path-params") 77 - ``` 40 + ## Level 2: Core HTMX Controls 78 41 79 - Available extensions: `event-header`, `path-params`, `remove-me`, `websocket`. 42 + - `hx-get` / `hx-post`: trigger requests. 43 + - `hx-target`: pick where response lands. 44 + - `hx-swap`: choose replacement strategy (`innerHTML`, `outerHTML`, `beforeend`). 45 + - `hx-trigger`: control event timing (`click`, `change`, `every 5s`, etc). 46 + - `hx-indicator`: show loading state. 80 47 81 - ### 3. Detect HTMX Requests 48 + ## Level 3: Advanced Server Patterns 82 49 83 - Use the `htmx.Is()` function to check if a request was made by HTMX: 50 + - Detect HTMX requests with `htmx.Is(r)` and return fragments. 51 + - Use out-of-band updates for multi-region refreshes. 52 + - Use response headers (`HX-Trigger`, `HX-Redirect`) for client behavior. 53 + - Preserve progressive enhancement: endpoints should still work without JS. 84 54 85 55 ```go 86 - import "xeiaso.net/v4/web/htmx" 87 - 88 - func handler(w http.ResponseWriter, r *http.Request) { 56 + func profileHandler(w http.ResponseWriter, r *http.Request) { 89 57 if htmx.Is(r) { 90 - // Return partial HTML fragment for HTMX 91 - components.Partial().Render(r.Context(), w) 92 - } else { 93 - // Return full page for direct navigation 94 - components.FullPage().Render(r.Context(), w) 95 - } 96 - } 97 - ``` 98 - 99 - ### 4. Create Interactive Component 100 - 101 - ```templ 102 - templ Counter(count int) { 103 - <div> 104 - <p>Count: { strconv.Itoa(count) }</p> 105 - <button 106 - hx-post="/counter/increment" 107 - hx-target="#counter" 108 - hx-swap="outerHTML" 109 - > 110 - Increment 111 - </button> 112 - </div> 113 - } 114 - ``` 115 - 116 - ### 5. Create Handler 117 - 118 - ```go 119 - func incrementHandler(w http.ResponseWriter, r *http.Request) { 120 - count := getCount() + 1 121 - saveCount(count) 122 - 123 - components.Counter(count).Render(r.Context(), w) 124 - } 125 - ``` 126 - 127 - ## Core HTMX Attributes 128 - 129 - ### hx-get / hx-post 130 - 131 - Trigger HTTP requests: 132 - 133 - ```templ 134 - templ SearchBox() { 135 - <input 136 - type="text" 137 - name="q" 138 - hx-get="/search" 139 - hx-trigger="keyup changed delay:500ms" 140 - hx-target="#results" 141 - /> 142 - <div id="results"></div> 143 - } 144 - ``` 145 - 146 - Handler: 147 - 148 - ```go 149 - func searchHandler(w http.ResponseWriter, r *http.Request) { 150 - query := r.URL.Query().Get("q") 151 - results := search(query) 152 - 153 - components.SearchResults(results).Render(r.Context(), w) 154 - } 155 - ``` 156 - 157 - ### hx-target 158 - 159 - Specify where to insert response: 160 - 161 - ```templ 162 - templ LoadMore(page int) { 163 - <button 164 - hx-get={ "/posts?page=" + strconv.Itoa(page) } 165 - hx-target="#posts" 166 - hx-swap="beforeend" 167 - > 168 - Load More 169 - </button> 170 - } 171 - ``` 172 - 173 - ### hx-swap 174 - 175 - Control how content is swapped: 176 - 177 - ```templ 178 - // innerHTML (default) 179 - hx-swap="innerHTML" 180 - 181 - // outerHTML - replace element itself 182 - hx-swap="outerHTML" 183 - 184 - // beforeend - append inside 185 - hx-swap="beforeend" 186 - 187 - // afterend - insert after 188 - hx-swap="afterend" 189 - ``` 190 - 191 - ### hx-trigger 192 - 193 - Control when requests fire: 194 - 195 - ```templ 196 - // On click (default for buttons) 197 - <button hx-get="/data">Click me</button> 198 - 199 - // On change 200 - <select hx-get="/filter" hx-trigger="change"> 201 - 202 - // On keyup with delay 203 - <input hx-get="/search" hx-trigger="keyup changed delay:300ms"> 204 - 205 - // On page load 206 - <div hx-get="/data" hx-trigger="load"> 207 - 208 - // Every 5 seconds 209 - <div hx-get="/updates" hx-trigger="every 5s"> 210 - ``` 211 - 212 - ## Common Patterns 213 - 214 - ### Pattern 1: Live Search 215 - 216 - Component: 217 - 218 - ```templ 219 - templ SearchBox() { 220 - <div> 221 - <input 222 - type="text" 223 - name="q" 224 - placeholder="Search..." 225 - hx-get="/search" 226 - hx-trigger="keyup changed delay:500ms" 227 - hx-target="#search-results" 228 - hx-indicator="#spinner" 229 - /> 230 - <span id="spinner" class="htmx-indicator"> 231 - Searching... 232 - </span> 233 - </div> 234 - <div id="search-results"></div> 235 - } 236 - 237 - templ SearchResults(results []string) { 238 - <ul> 239 - for _, result := range results { 240 - <li>{ result }</li> 241 - } 242 - </ul> 243 - } 244 - ``` 245 - 246 - Handler: 247 - 248 - ```go 249 - func searchHandler(w http.ResponseWriter, r *http.Request) { 250 - query := r.URL.Query().Get("q") 251 - results := performSearch(query) 252 - 253 - components.SearchResults(results).Render(r.Context(), w) 254 - } 255 - ``` 256 - 257 - ### Pattern 2: Infinite Scroll 258 - 259 - ```templ 260 - templ PostList(posts []Post, page int) { 261 - <div id="posts"> 262 - for _, post := range posts { 263 - @PostCard(post) 264 - } 265 - </div> 266 - 267 - if len(posts) > 0 { 268 - <div 269 - hx-get={ "/posts?page=" + strconv.Itoa(page+1) } 270 - hx-trigger="revealed" 271 - hx-swap="outerHTML" 272 - > 273 - Loading more... 274 - </div> 275 - } 276 - } 277 - ``` 278 - 279 - ### Pattern 3: Delete with Confirmation 280 - 281 - ```templ 282 - templ DeleteButton(itemID string) { 283 - <button 284 - hx-delete={ "/items/" + itemID } 285 - hx-confirm="Are you sure?" 286 - hx-target="closest tr" 287 - hx-swap="outerHTML swap:1s" 288 - > 289 - Delete 290 - </button> 291 - } 292 - ``` 293 - 294 - Handler: 295 - 296 - ```go 297 - func deleteHandler(w http.ResponseWriter, r *http.Request) { 298 - itemID := strings.TrimPrefix(r.URL.Path, "/items/") 299 - deleteItem(itemID) 300 - 301 - // Return empty to remove element 302 - w.WriteHeader(http.StatusOK) 303 - } 304 - ``` 305 - 306 - ### Pattern 4: Inline Edit 307 - 308 - ```templ 309 - templ EditableField(id string, value string) { 310 - <div id={ "field-" + id }> 311 - <span>{ value }</span> 312 - <button 313 - hx-get={ "/edit/" + id } 314 - hx-target={ "#field-" + id } 315 - hx-swap="outerHTML" 316 - > 317 - Edit 318 - </button> 319 - </div> 320 - } 321 - 322 - templ EditForm(id string, value string) { 323 - <form 324 - hx-post={ "/save/" + id } 325 - hx-target={ "#field-" + id } 326 - hx-swap="outerHTML" 327 - > 328 - <input type="text" name="value" value={ value } /> 329 - <button type="submit">Save</button> 330 - <button 331 - hx-get={ "/cancel/" + id } 332 - hx-target={ "#field-" + id } 333 - > 334 - Cancel 335 - </button> 336 - </form> 337 - } 338 - ``` 339 - 340 - ### Pattern 5: Form Validation 341 - 342 - ```templ 343 - templ SignupForm() { 344 - <form hx-post="/signup" hx-target="#form-errors"> 345 - <div id="form-errors"></div> 346 - 347 - <input 348 - type="email" 349 - name="email" 350 - hx-post="/validate/email" 351 - hx-trigger="blur" 352 - hx-target="#email-error" 353 - /> 354 - <div id="email-error"></div> 355 - 356 - <input type="password" name="password" /> 357 - 358 - <button type="submit">Sign Up</button> 359 - </form> 360 - } 361 - 362 - templ ValidationError(message string) { 363 - <span class="error">{ message }</span> 364 - } 365 - ``` 366 - 367 - ### Pattern 6: Polling / Real-time Updates 368 - 369 - ```templ 370 - templ LiveStats() { 371 - <div 372 - hx-get="/stats" 373 - hx-trigger="load, every 5s" 374 - hx-swap="innerHTML" 375 - > 376 - Loading stats... 377 - </div> 378 - } 379 - 380 - templ StatsDisplay(stats Stats) { 381 - <div> 382 - <p>Users online: { strconv.Itoa(stats.UsersOnline) }</p> 383 - <p>Active sessions: { strconv.Itoa(stats.Sessions) }</p> 384 - </div> 385 - } 386 - ``` 387 - 388 - ## Advanced Patterns 389 - 390 - ### Out-of-Band Updates (OOB) 391 - 392 - Update multiple parts of page: 393 - 394 - ```templ 395 - templ CartButton(count int) { 396 - <button id="cart-btn"> 397 - Cart ({ strconv.Itoa(count) }) 398 - </button> 399 - } 400 - 401 - templ AddToCartResponse(item Item) { 402 - // Main response 403 - <div class="notification"> 404 - Added { item.Name } to cart! 405 - </div> 406 - 407 - // Update cart button (different part of page) 408 - <div id="cart-btn" hx-swap-oob="true"> 409 - @CartButton(getCartCount()) 410 - </div> 411 - } 412 - ``` 413 - 414 - ### Progressive Enhancement 415 - 416 - ```templ 417 - templ Form() { 418 - <form 419 - action="/submit" 420 - method="POST" 421 - hx-post="/submit" 422 - hx-target="#result" 423 - > 424 - <input type="text" name="data" /> 425 - <button type="submit">Submit</button> 426 - </form> 427 - <div id="result"></div> 428 - } 429 - ``` 430 - 431 - Works without JavaScript, enhanced with HTMX. 432 - 433 - ### Loading States 434 - 435 - ```templ 436 - templ DataTable() { 437 - <div 438 - hx-get="/data" 439 - hx-trigger="load" 440 - hx-indicator="#loading" 441 - > 442 - <div id="loading" class="htmx-indicator"> 443 - Loading data... 444 - </div> 445 - </div> 446 - } 447 - ``` 448 - 449 - CSS: 450 - 451 - ```css 452 - .htmx-indicator { 453 - display: none; 454 - } 455 - 456 - .htmx-request .htmx-indicator { 457 - display: inline; 458 - } 459 - 460 - .htmx-request.htmx-indicator { 461 - display: inline; 462 - } 463 - ``` 464 - 465 - ## Response Headers 466 - 467 - ### HX-Trigger 468 - 469 - Trigger client-side events: 470 - 471 - ```go 472 - func handler(w http.ResponseWriter, r *http.Request) { 473 - // Do work... 474 - 475 - // Trigger custom event 476 - w.Header().Set("HX-Trigger", "itemCreated") 477 - 478 - components.Success().Render(r.Context(), w) 479 - } 480 - ``` 481 - 482 - Client side: 483 - 484 - ```javascript 485 - document.body.addEventListener("itemCreated", function (evt) { 486 - console.log("Item created!"); 487 - }); 488 - ``` 489 - 490 - ### HX-Redirect 491 - 492 - ```go 493 - func handler(w http.ResponseWriter, r *http.Request) { 494 - w.Header().Set("HX-Redirect", "/dashboard") 495 - w.WriteHeader(http.StatusOK) 496 - } 497 - ``` 498 - 499 - ### HX-Refresh 500 - 501 - ```go 502 - func handler(w http.ResponseWriter, r *http.Request) { 503 - w.Header().Set("HX-Refresh", "true") 504 - w.WriteHeader(http.StatusOK) 505 - } 506 - ``` 507 - 508 - ### Special Status Code 509 - 510 - Stop polling with status code 286: 511 - 512 - ```go 513 - import "within.website/x/htmx" 514 - 515 - func pollHandler(w http.ResponseWriter, r *http.Request) { 516 - if shouldStopPolling() { 517 - w.WriteHeader(htmx.StatusStopPolling) 58 + _ = components.ProfilePanel().Render(r.Context(), w) 518 59 return 519 60 } 520 - // ... return normal content 521 - } 522 - ``` 523 - 524 - ### Request Headers 525 - 526 - Access HTMX request headers: 527 - 528 - ```go 529 - import "within.website/x/htmx" 530 - 531 - func handler(w http.ResponseWriter, r *http.Request) { 532 - // Check if this is an HTMX request 533 - if htmx.Is(r) { 534 - // Get user's response to hx-prompt 535 - promptResponse := r.Header.Get(htmx.HeaderPrompt) 536 - } 537 - } 538 - ``` 539 - 540 - ## Best Practices 541 - 542 - 1. **Keep handlers focused** - Return only the HTML fragment needed 543 - 2. **Use semantic HTML** - Works without JS 544 - 3. **Handle errors gracefully** - Return error components 545 - 4. **Optimize responses** - Send minimal HTML 546 - 5. **Use OOB for multi-updates** - Update multiple page sections 547 - 6. **Progressive enhancement** - Always provide fallback 548 - 549 - ## Full Example: Todo App 550 - 551 - ```templ 552 - // components/todo.templ 553 - package components 554 - 555 - type Todo struct { 556 - ID string 557 - Text string 558 - Completed bool 559 - } 560 - 561 - templ TodoApp(todos []Todo) { 562 - @Layout("Todo App") { 563 - <div> 564 - <h1>My Todos</h1> 565 - 566 - @TodoForm() 567 - @TodoList(todos) 568 - </div> 569 - } 570 - } 571 - 572 - templ TodoForm() { 573 - <form 574 - hx-post="/todos" 575 - hx-target="#todo-list" 576 - hx-swap="beforeend" 577 - hx-on::after-request="this.reset()" 578 - > 579 - <input 580 - type="text" 581 - name="text" 582 - placeholder="New todo..." 583 - required 584 - /> 585 - <button type="submit">Add</button> 586 - </form> 587 - } 588 - 589 - templ TodoList(todos []Todo) { 590 - <ul id="todo-list"> 591 - for _, todo := range todos { 592 - @TodoItem(todo) 593 - } 594 - </ul> 595 - } 596 - 597 - templ TodoItem(todo Todo) { 598 - <li id={ "todo-" + todo.ID }> 599 - <input 600 - type="checkbox" 601 - checked?={ todo.Completed } 602 - hx-post={ "/todos/" + todo.ID + "/toggle" } 603 - hx-target={ "#todo-" + todo.ID } 604 - hx-swap="outerHTML" 605 - /> 606 - <span class={ templ.KV("completed", todo.Completed) }> 607 - { todo.Text } 608 - </span> 609 - <button 610 - hx-delete={ "/todos/" + todo.ID } 611 - hx-target={ "#todo-" + todo.ID } 612 - hx-swap="outerHTML swap:500ms" 613 - > 614 - Delete 615 - </button> 616 - </li> 61 + _ = components.ProfilePage().Render(r.Context(), w) 617 62 } 618 63 ``` 619 64 620 - Handlers: 65 + ## Escalate to Other Skills 621 66 622 - ```go 623 - func todosHandler(w http.ResponseWriter, r *http.Request) { 624 - switch r.Method { 625 - case "GET": 626 - todos := getAllTodos() 627 - components.TodoApp(todos).Render(r.Context(), w) 628 - 629 - case "POST": 630 - r.ParseForm() 631 - todo := createTodo(r.FormValue("text")) 632 - components.TodoItem(todo).Render(r.Context(), w) 633 - } 634 - } 635 - 636 - func todoToggleHandler(w http.ResponseWriter, r *http.Request) { 637 - id := extractID(r.URL.Path) 638 - todo := toggleTodo(id) 639 - components.TodoItem(todo).Render(r.Context(), w) 640 - } 641 - 642 - func todoDeleteHandler(w http.ResponseWriter, r *http.Request) { 643 - id := extractID(r.URL.Path) 644 - deleteTodo(id) 645 - w.WriteHeader(http.StatusOK) // Empty response removes element 646 - } 647 - ``` 67 + - Need handler/routing structure: use `templ-http`. 68 + - Need reusable component APIs: use `templ-components`. 69 + - Need template syntax help: use `templ-syntax`. 648 70 649 - ## Resources 71 + ## References 650 72 651 - - [HTMX Documentation](https://htmx.org/docs/) 652 - - [HTMX Examples](https://htmx.org/examples/) 653 - - [Hypermedia Systems Book](https://hypermedia.systems/) 73 + - Quick start: `resources/quick-start.md` 74 + - Interaction patterns: `resources/interaction-patterns.md` 75 + - Advanced responses: `resources/advanced-responses.md` 76 + - HTMX docs: https://htmx.org/docs/ 77 + - Hypermedia Systems: https://hypermedia.systems/
+35
.claude/skills/templ-htmx/resources/advanced-responses.md
··· 1 + # Templ + HTMX Advanced Responses 2 + 3 + ## Fragment-Based Partial Responses 4 + 5 + Use `htmx.Is(r)` to decide whether to return a full page or an HTMX-targeted partial. 6 + 7 + ```go 8 + func profileHandler(w http.ResponseWriter, r *http.Request) { 9 + ctx := templ.WithFragments(r.Context(), "profile-panel") 10 + _ = components.ProfilePage().Render(ctx, w) 11 + } 12 + ``` 13 + 14 + - Define fragment blocks in templates with `@templ.Fragment("profile-panel") { ... }`. 15 + - Nested fragments can be selectively rendered. 16 + - Important: fragment filtering limits output, but non-fragment logic still runs. 17 + 18 + ## Rendering Fragments Outside HTTP 19 + 20 + - Use `templ.RenderFragments(...)` when writing to buffers/files. 21 + 22 + ## Streaming for Progressive Delivery 23 + 24 + - `templ.WithStreaming()` enables progressive output. 25 + - `@templ.Flush()` can improve perceived latency for long renders. 26 + - Tradeoff: after bytes are written, header/status changes are constrained. 27 + 28 + ## External HTMX Concepts 29 + 30 + - Headers like `HX-Trigger`, `HX-Redirect`, and `HX-Refresh` are HTMX features, but they are not documented as templ-specific patterns in templ.guide. 31 + 32 + ## Sources 33 + 34 + - https://templ.guide/syntax-and-usage/fragments 35 + - https://templ.guide/server-side-rendering/streaming
+41
.claude/skills/templ-htmx/resources/interaction-patterns.md
··· 1 + # Templ + HTMX Interaction Patterns 2 + 3 + Project standard: use `import "xeiaso.net/v4/web/htmx"` with `@htmx.Use()` in layout, rather than manually adding script tags. 4 + 5 + ## Core Attributes 6 + 7 + - `hx-get`, `hx-post`, `hx-delete` 8 + - `hx-target` 9 + - `hx-swap` (`innerHTML`, `outerHTML`, `beforeend`) 10 + - `hx-trigger` (`click`, `blur`, `every 5s`, etc) 11 + - `hx-indicator` 12 + 13 + Use `templ.URL(...)` for URL-valued custom attrs such as `hx-get` and `hx-post` when values are dynamic. 14 + 15 + ## Common Patterns 16 + 17 + - Live search with delayed keyup trigger. 18 + - Infinite scroll with `hx-trigger="revealed"`. 19 + - Inline edit by swapping display row with form row. 20 + - Form validation via field-level HTMX requests. 21 + 22 + ## Boosted Navigation and Forms 23 + 24 + - `hx-boost="true"` upgrades regular links/forms to HTMX behavior. 25 + - Keep standard links/forms semantics so direct navigation still works. 26 + 27 + ## Event Handlers in Attributes 28 + 29 + - For `hx-on:*` or `on*`, use static handler strings for simple cases. 30 + - Use `templ.JSFuncCall(...)` for safer dynamic JS argument encoding. 31 + 32 + ## Progressive Enhancement 33 + 34 + Always include normal `action`/`method` on forms and server endpoints that work without JS. 35 + 36 + ## Sources 37 + 38 + - https://templ.guide/server-side-rendering/htmx 39 + - https://templ.guide/syntax-and-usage/attributes 40 + - https://templ.guide/syntax-and-usage/forms 41 + - https://templ.guide/syntax-and-usage/script-templates
+67
.claude/skills/templ-htmx/resources/quick-start.md
··· 1 + # Templ + HTMX Quick Start 2 + 3 + ## Required Integration Pattern 4 + 5 + This project requires: 6 + 7 + - `import "xeiaso.net/v4/web/htmx"` 8 + - `htmx.Mount(mux)` in server setup 9 + - `@htmx.Use()` in the layout `<head>` 10 + - `htmx.Is(r)` checks in handlers to branch full-page vs fragment responses 11 + 12 + ## Server Setup 13 + 14 + ```go 15 + import "xeiaso.net/v4/web/htmx" 16 + 17 + func main() { 18 + mux := http.NewServeMux() 19 + htmx.Mount(mux) 20 + // register app routes 21 + } 22 + ``` 23 + 24 + ## Layout Setup 25 + 26 + ```templ 27 + import "xeiaso.net/v4/web/htmx" 28 + 29 + templ Layout() { 30 + <html> 31 + <head> 32 + @htmx.Use() 33 + </head> 34 + <body>{ children... }</body> 35 + </html> 36 + } 37 + ``` 38 + 39 + ## First Interactive Action 40 + 41 + - Add `hx-post` or `hx-get` to a button/input. 42 + - Use `hx-target` and `hx-swap` to control replacement. 43 + - Use `hx-select` when only part of response HTML should be swapped. 44 + - Keep normal form `action`/`method` for no-JS fallback. 45 + 46 + ## Request Detection With `htmx.Is` 47 + 48 + ```go 49 + import "xeiaso.net/v4/web/htmx" 50 + 51 + func profileHandler(w http.ResponseWriter, r *http.Request) { 52 + if htmx.Is(r) { 53 + _ = components.ProfilePanel().Render(r.Context(), w) 54 + return 55 + } 56 + _ = components.ProfilePage().Render(r.Context(), w) 57 + } 58 + ``` 59 + 60 + ## Fragment Optimization 61 + 62 + When templ responses are large, combine HTMX with templ fragments (`templ.WithFragments`) to return only the needed section. 63 + 64 + ## Sources 65 + 66 + - https://templ.guide/server-side-rendering/htmx 67 + - https://templ.guide/syntax-and-usage/fragments