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