Skip to main content

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.

tip

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:

HeaderEffect
HX-RedirectClient-side redirect
HX-RefreshFull page refresh
HX-TriggerTrigger client events
HX-ReswapChange swap method
HX-RetargetChange target element
HX-TitleUpdate 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-Title response 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.

Resources​