Implements HTMX interactions, configures swap behaviors, debugs hx-* requests, and builds hypermedia-driven UI components. Use when tasks involve hx-* attributes, HTMX AJAX requests, swap strategies, server-sent events, WebSockets, or hypermedia-driven UIs.
95
95%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
How HTMX issues requests, what gets sent, and how to control the lifecycle.
The order of operations for an HTMX request:
htmx-request class is applied to the element (or hx-indicator target)htmx-swapping class is added to the targetswap:<time> modifier)htmx-settling class is applied to the targethtmx-added class removed from new content| Class | Applied To | When |
|---|---|---|
htmx-request | Triggering element or hx-indicator target | During the request |
htmx-swapping | Target element | After response, before swap |
htmx-settling | Target element | After swap, before settle completes |
htmx-added | New content elements | After swap, removed after settle |
Use these classes for loading indicators, animations, and transitions.
HTMX automatically includes these headers with every request:
| Header | Value | Purpose |
|---|---|---|
HX-Boosted | true | Present when request is via hx-boost |
HX-Current-URL | Current browser URL | Lets server know the user's current page |
HX-History-Restore-Request | true | Present when request is for history cache miss restoration |
HX-Prompt | User's response | Present when hx-prompt was used |
HX-Request | true | Always present on HTMX requests |
HX-Target | Target element's id | The target element for the response |
HX-Trigger | Triggering element's id | The element that triggered the request |
HX-Trigger-Name | Triggering element's name | The name attribute of the triggering element |
Use HX-Request: true to detect HTMX requests and return partial HTML instead of full pages:
# Python/Flask example
if request.headers.get('HX-Request'):
return render_template('partial.html')
return render_template('full_page.html')// Node.js/Express example
if (req.headers['hx-request']) {
return res.render('partial');
}
res.render('full_page');The server can control client behavior using these response headers:
| Header | Effect |
|---|---|
HX-Location | Client-side redirect (AJAX, no full page reload). Value: URL string or JSON {"path": "/url", "target": "#el"} |
HX-Push-Url | Push URL into browser history. Value: URL string or false |
HX-Redirect | Full-page client-side redirect (non-AJAX) |
HX-Refresh | Full page refresh. Value: true |
HX-Replace-Url | Replace current URL in location bar (no new history entry). Value: URL string or false |
HX-Reswap | Override the hx-swap value for this response. Value: any valid swap strategy |
HX-Retarget | Override the hx-target value. Value: CSS selector |
HX-Reselect | Override the hx-select value. Value: CSS selector |
HX-Trigger | Trigger client-side events. Value: event name or JSON {"event": {"key": "value"}} |
HX-Trigger-After-Settle | Trigger events after the settle phase |
HX-Trigger-After-Swap | Trigger events after the swap phase |
HX-Trigger: myEventWith data:
HX-Trigger: {"showMessage": {"level": "info", "message": "Item saved!"}}Multiple events:
HX-Trigger: {"event1": null, "event2": {"key": "value"}}HTMX does not need the traditional POST/Redirect/GET pattern. After a successful POST, the server can return new HTML directly — no 302 redirect needed.
A 204 No Content response performs no swap. Useful for fire-and-forget requests.
htmx:responseError eventhtmx:sendError eventImportant: Response headers (
HX-Trigger,HX-Retarget, etc.) are NOT processed on 3xx redirect responses. Use alternative status codes when returning htmx-specific headers.
Override default behavior per status code using htmx.config.responseHandling:
htmx.config.responseHandling = [
{ code: "204", swap: false },
{ code: "[23]..", swap: true },
{ code: "422", swap: true, error: true },
{ code: "[45]..", swap: false, error: true },
// Custom: swap 404 responses into a specific target
{ code: "404", swap: true, target: "#error-container" }
];Fields per entry:
| Field | Type | Purpose |
|---|---|---|
code | String (regex) | Status code pattern to match |
swap | Boolean | Whether to swap content into DOM |
error | Boolean | Whether to treat as error (fires error events) |
ignoreTitle | Boolean | Skip <title> updates |
select | String | CSS selector for response content |
target | String | Alternative target selector |
swapOverride | String | Custom swap strategy |
| Scenario | Included Values |
|---|---|
| Input/select/textarea triggers request | Its own name/value pair |
| Element inside a form (non-GET) | All form input values |
hx-include specified | Values from the targeted elements |
hx-vals specified | Additional JSON key-value pairs |
hx-params specified | Filtered subset of values |
| Method | Encoding |
|---|---|
| GET, DELETE | URL query string parameters |
| POST, PUT, PATCH | URL-encoded form body (application/x-www-form-urlencoded) |
With hx-encoding="multipart/form-data" | Multipart form body |
Use the htmx:configRequest event:
<div hx-get="/data" hx-on:htmx:config-request="event.detail.parameters.extra = 'value'">
Load
</div>Or globally:
document.body.addEventListener('htmx:configRequest', function(event) {
event.detail.parameters['auth'] = getToken();
event.detail.headers['X-Custom'] = 'value';
});Set hx-encoding="multipart/form-data" to enable file uploads:
<form hx-post="/upload" hx-encoding="multipart/form-data">
<input type="file" name="document" />
<button type="submit">Upload</button>
</form>Track upload progress via the htmx:xhr:progress event:
<form hx-post="/upload" hx-encoding="multipart/form-data"
hx-on:htmx:xhr:progress="updateProgress(event)">
<input type="file" name="document" />
<progress id="progress" value="0" max="100"></progress>
<button type="submit">Upload</button>
</form>
<script>
function updateProgress(event) {
var progress = event.detail.loaded / event.detail.total * 100;
document.getElementById('progress').value = progress;
}
</script>Place on <body> or a high-level element to inherit to all requests:
<body hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token }}"}'>
...
</body>document.body.addEventListener('htmx:configRequest', function(event) {
event.detail.headers['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').content;
});Most server frameworks auto-insert CSRF tokens into <form> elements. For non-form HTMX requests, use one of the above approaches.
For cross-origin requests, configure the server to allow HTMX headers:
Access-Control-Allow-Headers: HX-Request, HX-Trigger, HX-Trigger-Name, HX-Target, HX-Current-URL, HX-Boosted, HX-Prompt
Access-Control-Expose-Headers: HX-Location, HX-Push-Url, HX-Redirect, HX-Refresh, HX-Replace-Url, HX-Reswap, HX-Retarget, HX-Reselect, HX-Trigger, HX-Trigger-After-Settle, HX-Trigger-After-SwapNote: htmx.config.selfRequestsOnly = true (default) restricts requests to the same domain. Set to false for cross-origin requests, or use the htmx:validateUrl event for fine-grained control.
HTMX respects standard HTTP caching headers:
| Header | Behavior |
|---|---|
Last-Modified | HTMX sends If-Modified-Since on subsequent requests |
ETag | HTMX sends If-None-Match on subsequent requests |
Cache-Control | Standard browser caching applies |
Use Vary: HX-Request to prevent caching HTMX partial responses as full pages:
Vary: HX-Requesthtmx.config.getCacheBusterParam = true;Appends a unique org.htmx.cache-buster query parameter to GET requests.
Always escape all user-supplied content server-side. Whitelist allowed HTML tags/attributes rather than blacklisting.
Prevents HTMX processing on an element and all children. Use to protect user-content areas:
<div hx-disable>
{{ user_generated_content }}
</div><meta http-equiv="Content-Security-Policy" content="default-src 'self';">// Restrict to same-origin requests only (default: true)
htmx.config.selfRequestsOnly = true;
// Disable eval-dependent features
htmx.config.allowEval = false;
// Disable script tag processing in responses
htmx.config.allowScriptTags = false;
// Disable history cache (no localStorage usage)
htmx.config.historyCacheSize = 0;Use htmx:validateUrl to inspect and block requests:
document.body.addEventListener('htmx:validateUrl', function(event) {
if (!event.detail.sameHost) {
event.preventDefault(); // Block cross-origin request
}
});HTMX integrates with the HTML5 Validation API. If a form element is invalid, the request is blocked.
<form hx-post="/submit">
<input name="email" type="email" required />
<button type="submit">Submit</button>
</form><input hx-get="/check" hx-validate="true" required pattern="[a-z]+" />| Event | When |
|---|---|
htmx:validation:validate | Before checkValidity() is called |
htmx:validation:failed | After a validation failure |
htmx:validation:halted | When a request is halted due to validation |
htmx.config.reportValidityOfForms = true; // default: false<button hx-delete="/item/1" hx-confirm="Delete this item?">Delete</button>Use the htmx:confirm event for non-native dialogs:
document.body.addEventListener('htmx:confirm', function(event) {
event.preventDefault(); // Halt the request
showCustomDialog(event.detail.question).then(function(confirmed) {
if (confirmed) {
event.detail.issueRequest(); // Continue the request
}
});
});<button hx-post="/rename" hx-prompt="Enter new name:">Rename</button>The user's response is available in the HX-Prompt request header.