The Hypermedia Model
An anchor tag is already a hypermedia control: it issues a GET and swaps the response into the whole window. htmx generalizes that one idea - any element, any event, any HTTP verb, any target:
<!-- anchor: click -> GET -> replace the window -->
<a href="/stories">Stories</a>
<!-- htmx: click -> GET -> replace just this div -->
<div hx-get="/stories/page2" hx-target="this">
Load more
</div>
<!-- any element, any event, any verb -->
<button hx-delete="/stories/42" hx-target="closest article">
Delete
</button>
The browser does the work. htmx intercepts the event, issues the request with a few HX-* headers, and swaps the response body into the target. No JSON parsing, no client-side template layer, no virtual DOM.
Here is the full request lifecycle for a “load next page” button:
- The server sees
HX-Request: trueand can return a fragment instead of a full page. - The response body is the new DOM, verbatim. The server decides what the UI becomes; the client just swaps it in.
- After the swap, htmx settles for the configured 20 ms (default,
src/htmx.js:112), giving CSS transitions time to run between the old and new state.
Fragments, not JSON
In a JSON-API app the server ships data and the client decides how to render it. In a hypermedia app the server ships rendered HTML and the decision is already made. This keeps the server as the owner of application state and UI structure - HATEOAS in practice, without the theory.
JSON API response: htmx response:
{ "stories": [ ... ], <li class="story">...</li>
"nextCursor": "abc", <li class="story">...</li>
"unreadCount": 3 } <li class="story">...</li>
client must render all of it swap it in; done
+ a second request for the badge <hx-partial id="badge">3</hx-partial>
One response can update several parts of the page using out-of-band swaps or hx-partial envelopes - see Server Integration.
Locality of Behaviour
Behavior lives next to the element it affects. To know what a button does, you read the button:
<button hx-post="/subscribe"
hx-trigger="click"
hx-confirm="Subscribe to the weekly digest?">
Subscribe
</button>
The contrast is a centralized controller that binds handlers to selectors in a separate file. Locality costs you nothing here because there is no build step to fight; the trade-off is that logic is spread across markup, so naming and server-side structure carry more weight. The canonical argument is Carson Gross’s essay in the repo: locality-of-behaviour.md.
Hypermedia-Driven Applications
The term for apps built this way is Hypermedia-Driven Application (HDA): server-rendered HTML, hypermedia exchanges for all mutations, and scripting as an enhancement rather than a requirement. Most HDAs still use some JavaScript - a chart library, a map - but state transitions flow through HTML responses. See hypermedia-driven-applications.md and the practical 10 tips for SSR HDA apps.
Where to next:
- Core Attributes - the full attribute surface: triggers, targets, swaps, parameters
- Server Integration - headers, status codes, and multi-update mechanics
- First Page - build a working page in Go