HTMX Patterns
Common HTMX patterns for building interactive web applications.
Looking for a complete tutorial? See the HTMX & Fragments Guide for step-by-step integration with Luat forms and fragments.
Setup
CDN
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
npm
npm install htmx.org
// assets/js/app.ts
import htmx from 'htmx.org';
window.htmx = htmx;
hx-boost
Add hx-boost="true" to <body> for automatic AJAX navigation on all links and forms:
<body hx-boost="true">
...
</body>
Swap Patterns
innerHTML (default)
Replace element's content:
<div hx-get="/content" hx-swap="innerHTML">
<!-- Content replaced here -->
</div>
outerHTML
Replace entire element:
<div hx-get="/content" hx-swap="outerHTML">
<!-- Entire div replaced -->
</div>
beforeend / afterbegin
Append or prepend to element:
<ul hx-get="/items" hx-swap="beforeend">
<!-- New items added at end -->
</ul>
delete
Remove element after request:
<button hx-delete="/item/1" hx-swap="delete">Delete</button>
Targeting
hx-target
Specify where to swap content:
<button hx-get="/content" hx-target="#result">Load</button>
<div id="result"></div>
Relative targets
<button hx-get="/content" hx-target="closest div">Load</button>
<button hx-get="/content" hx-target="next .result">Load</button>
<button hx-get="/content" hx-target="previous li">Load</button>
Triggers
Default triggers
<input>,<textarea>,<select>:change<form>:submit- Everything else:
click
Custom triggers
<!-- Trigger on keyup with delay -->
<input hx-get="/search" hx-trigger="keyup changed delay:300ms">
<!-- Trigger on multiple events -->
<div hx-get="/data" hx-trigger="load, click">
<!-- Trigger once -->
<div hx-get="/data" hx-trigger="load once">
<!-- Trigger when visible -->
<div hx-get="/data" hx-trigger="revealed">
Passing Data
hx-vals
Pass JSON values:
<button hx-post="/action" hx-vals='{"id": "123", "type": "delete"}'>
Delete
</button>
Dynamic Values with Luat
When using hx-vals with dynamic data in Luat templates, use data-* attributes combined with HTMX's js: prefix and escaped braces:
<button
data-id="{todo.id}"
hx-post="?/toggle"
hx-vals="js:\{id: this.dataset.id\}"
hx-target="#todo-{todo.id}"
>
Toggle
</button>
The \{ and \} escape sequences output literal braces, so hx-vals renders as js:{id: this.dataset.id}. HTMX evaluates this JavaScript expression at runtime to read from the button's data attribute.
See Escaping Curly Braces for more details on the escape syntax.
hx-include
Include form fields:
<input id="search" name="q">
<button hx-get="/search" hx-include="#search">Search</button>
Loading States
hx-indicator
Show loading indicator:
<button hx-get="/data" hx-indicator="#spinner">
Load
</button>
<span id="spinner" class="htmx-indicator">Loading...</span>
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }
.htmx-request.htmx-indicator { display: inline; }
hx-disabled-elt
Disable elements during request:
<button hx-post="/save" hx-disabled-elt="this">
Save
</button>
Out-of-Band Updates
Update multiple elements with one response:
<!-- Main response swapped normally -->
<div id="main">Updated content</div>
<!-- These update their targets regardless of hx-target -->
<div id="sidebar" hx-swap-oob="true">New sidebar</div>
<span id="count" hx-swap-oob="true">42</span>
Response Headers
Control HTMX from server responses:
| Header | Effect |
|---|---|
HX-Redirect | Client-side redirect |
HX-Refresh | Full page refresh |
HX-Trigger | Trigger client events |
HX-Reswap | Change swap method |
HX-Retarget | Change target element |
HX-Title | Update document title (requires extension) |
return {
body = "content",
headers = {
["HX-Trigger"] = "itemSaved",
["HX-Reswap"] = "innerHTML"
}
}
Events
Listen to HTMX events
document.body.addEventListener('htmx:afterSwap', (event) => {
console.log('Swapped:', event.detail.target);
});
document.body.addEventListener('htmx:beforeRequest', (event) => {
console.log('Requesting:', event.detail.path);
});
Trigger custom events
<div hx-trigger="customEvent from:body" hx-get="/refresh">
htmx.trigger(document.body, 'customEvent');
Extensions
HTMX can be extended with additional capabilities:
- Idiomorph - Intelligent DOM morphing that preserves focus and form state
- View Transitions - Smooth animations when content changes
- @maravilla-labs/htmx-ext-title - Update document title from
HX-Titleresponse header
Title Extension
The title extension updates the browser's document title when Luat sends the HX-Title response header. This is essential for proper browser history when using hx-boost or AJAX navigation.
Installation:
npm install @maravilla-labs/htmx-ext-title
Setup in app.ts:
import htmx from 'htmx.org';
import { title } from '@maravilla-labs/htmx-ext-title';
title(htmx);
Enable in app.html:
<body hx-boost="true" hx-ext="title">
%luat.body%
</body>
How it works:
When you set view_title via setPageContext("view_title", "My Page") in your templates or loaders, Luat automatically sends this as an HX-Title response header. The extension reads this header and updates document.title.
For detailed setup with Luat forms and fragments, see the HTMX & Fragments Guide.