htmx Tutorial · Going Further · Lesson 24 of 30
Response Headers
Let the server control what happens next: redirect, refresh, retarget the swap, change the swap style, or push a URL.
Headers the server can send
| Header | Effect |
|---|---|
HX-Trigger |
fire client-side events (a name, or JSON with details: {"showMessage": "Saved"}) |
HX-Redirect |
do a full browser redirect to a URL |
HX-Location |
navigate to a URL with an htmx request (no full reload) |
HX-Refresh |
true: reload the whole page |
HX-Push-Url / HX-Replace-Url |
change the address bar |
HX-Retarget |
swap into a different element (a CSS selector) |
HX-Reswap |
use a different swap style |
HX-Reselect |
choose part of the response, like hx-select |
These let the server decide based on the result. A login form, for example, returns the form with an error, or
HX-Redirect: /dashboard on success. (HX-Redirect is used instead of a normal 302 because htmx's request would
follow a 302 silently and swap the dashboard HTML into the form.)
<form hx-post="/comments" hx-target="#comments" hx-swap="beforeend">
<input name="text" placeholder="Write a comment (try 'spam')"> <button>Post</button>
</form>
<ul id="comments"></ul>
<p id="flash" style="color:#b91c1c"></p>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.post("/comments", (req) => {
const text = req.body.text || "";
if (/spam/i.test(text)) // the server changes where and how the response is swapped
return { body: "Comments that look like spam aren't allowed.",
headers: { "HX-Retarget": "#flash", "HX-Reswap": "innerHTML" } };
return `<li>${server.escape(text)}</li>`;
});
</script>
Sending data with HX-Trigger
HX-Trigger accepts JSON. Each key is an event name, and its value arrives in event.detail.value. The event is
fired on the element that made the request and bubbles up, so any ancestor (or document.body) can listen. A common
use is a toast message. (Use lower-case, dash-separated event names with hx-on: HTML attribute names can't keep
capital letters.)
<style>#toast { position: fixed; right: 8px; top: 8px; background: #15803d; color: #fff; padding: 6px 10px;
border-radius: 6px; display: none; }</style>
<!-- HX-Trigger fires the event on the button; it bubbles up to this div -->
<div hx-on:show-message="const t = document.getElementById('toast'); t.textContent = event.detail.value;
t.style.display = 'block'; setTimeout(() => t.style.display = 'none', 2000)">
<button hx-post="/settings" hx-swap="none">Save settings</button>
</div>
<div id="toast"></div>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.post("/settings", () => ({ body: "", headers: { "HX-Trigger": JSON.stringify({ "show-message": "Settings saved" }) } }));
</script>
Test yourself
Why use HX-Redirect instead of a normal 302 redirect for an htmx login form?
htmx's request follows a 302 silently and would swap the new page into the form302 is not allowed for POSTHX-Redirect is fasterBrowsers block 302 responses to htmx
Show answer
Answer: htmx's request follows a 302 silently and would swap the new page into the form
XMLHttpRequest follows redirects transparently; HX-Redirect asks htmx to change the whole page instead.