htmx Tutorial · htmx Basics · Lesson 3 of 30
How htmx Works
The request-swap cycle step by step, what htmx sends to the server, and how to read the request log.
The htmx cycle
Every htmx interaction follows the same five steps:
- Trigger: an event happens on an element with an htmx attribute (by default a click; a
changefor inputs; asubmitfor forms). - Request: htmx sends an HTTP request, using the method and URL from
hx-get,hx-post, etc. It includes any form values and some extra request headers. - Response: the server returns HTML (usually a small fragment, not a whole page).
- Swap: htmx puts that HTML into the target element (
hx-target, by default the element itself), in the wayhx-swapsays (by default it replaces the target's inner HTML). - Settle: htmx processes the new content, so htmx attributes in the response work too.
Step 5 is important: the server's HTML can contain more hx- attributes, and the cycle continues from there.
<div id="step">
<p>Step 1 of 3</p>
<button hx-get="/step/2" hx-target="#step">Next</button>
</div>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
// Each response contains a new button with its own hx-get: the cycle continues.
server.get("/step/:n", (req) => {
const n = Number(req.params.n);
return n < 3
? `<p>Step ${n} of 3</p><button hx-get="/step/${n + 1}" hx-target="#step">Next</button>`
: `<p>Step 3 of 3. Done!</p><button hx-get="/step/1" hx-target="#step">Start again</button>`;
});
</script>
Headers htmx adds to each request
Your server can tell an htmx request from a normal page load by its headers:
| Header | Value |
|---|---|
HX-Request |
always true |
HX-Trigger |
the id of the element that was triggered, if it has one |
HX-Trigger-Name |
the name of the triggered element, if it has one |
HX-Target |
the id of the target element, if it has one |
HX-Current-URL |
the current URL of the browser |
HX-Boosted |
true if the request comes from a boosted link or form |
The most useful one is HX-Request: the same URL can return a full page to a normal visit and just the
fragment to htmx. This example shows the headers the server received:
<button id="who" name="greeting" hx-get="/headers" hx-target="#out">Send a request</button>
<pre id="out"></pre>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.get("/headers", (req) => {
const h = req.headers;
return server.escape(`HX-Request: ${h["hx-request"]}\nHX-Trigger: ${h["hx-trigger"]}\n` +
`HX-Trigger-Name: ${h["hx-trigger-name"]}\nHX-Target: ${h["hx-target"]}`);
});
</script>
What if the server returns an error?
In htmx 2, responses with status 4xx or 5xx are not swapped by default: the page stays as it was and htmx fires
an htmx:responseError event. A 204 No Content response is not swapped either. You'll learn how to show error
messages on the forms page.
Test yourself
In htmx 2, what happens by default when the server answers with status 500?
Nothing is swapped and htmx fires htmx:responseErrorThe error page replaces the whole documenthtmx retries the request three timesThe response is swapped into the target like any other
Show answer
Answer: Nothing is swapped and htmx fires htmx:responseError
4xx and 5xx responses aren't swapped in htmx 2 unless you change htmx.config.responseHandling.