HTMX: How HTML Learned to Talk Back
Hyperlinks and forms have quietly made HTTP requests since 1993. HTMX asks one question — what if any element could do that, on any event, swapping any piece of the page?
Click a link. The page makes a request to a server. The server sends back some HTML. The browser throws away the old page and shows the new one.
You have been using a hypermedia client — a browser — your whole life, and that little loop is the whole engine. HTML has known how to talk to servers since the very beginning. The catch is that it only lets two elements do it (<a> and <form>), only on two events (click and submit), with only two verbs (GET and POST), and the response always replaces the entire page.
HTMX is a small JavaScript library with a deceptively simple thesis: those four limits are arbitrary. Lift them, and you can build rich, interactive interfaces without leaving HTML.
The four restrictions, lifted
Almost everything HTMX does follows from removing one of HTML's built-in constraints. Here is the entire conceptual surface in one table — the rest of this fable is just unpacking it.
| Plain HTML can only… | HTMX lets you… | Attribute |
|---|---|---|
| make requests from <a> and <form> | make a request from ANY element | hx-get, hx-post |
| trigger on click / submit | trigger on ANY DOM event | hx-trigger |
| use GET and POST | use GET, POST, PUT, PATCH, DELETE | hx-put, hx-patch, hx-delete |
| replace the whole page | replace ANY element you target | hx-target, hx-swap |
That is genuinely most of it. There is no virtual DOM, no client-side router, no JSON serialization layer, no build step required. A <div> can issue a PATCH request when it scrolls into view and drop the response into a different <div> somewhere else on the page.
Check yourself
In plain HTML (no JavaScript), which elements can make an HTTP request on their own?
Anatomy of a swap
Let's watch one HTMX interaction end to end. Suppose we want a "Load more" button that appends the next page of comments — no full reload, no spinner-juggling.
<button hx-get="/comments?page=2"
hx-target="#comment-list"
hx-swap="beforeend">
Load more
</button>
<ul id="comment-list">
<li>First comment…</li>
</ul>
Here is what happens when someone clicks, step by step.
- 1
Trigger fires
The button's default trigger for a non-form element is
click. HTMX is listening. (We could change this withhx-trigger="mouseenter"orkeyup changed delay:500msand so on.) - 2
Request goes out
HTMX issues
GET /comments?page=2in the background viafetch. It also adds request headers likeHX-Request: true, so your server can tell an HTMX call apart from a full-page navigation and respond accordingly. - 3
Server returns HTML — not JSON
This is the philosophical heart of it. The server replies with a fragment of ready-to-display HTML:
<li>Another comment…</li><li>And another…</li>. No client-side templating. The server already knows how to render comments; it just renders fewer of them. - 4
HTMX finds the target
hx-target="#comment-list"points at the<ul>. Without a target, HTMX swaps into the element that triggered the request — here we redirect it elsewhere. - 5
The swap happens
hx-swap="beforeend"inserts the new<li>s as the last children of the list, leaving existing comments untouched. The page updates. No reload. Done.
Where the response goes: hx-swap
The target says which element; the swap says how the response lands relative to it. This is a small, learnable vocabulary.
| hx-swap | What it does |
|---|---|
| innerHTML | Replace the target's contents (the default) |
| outerHTML | Replace the target element itself, tag and all |
| beforeend | Append inside, after the last child |
| afterbegin | Insert inside, before the first child |
| beforebegin | Insert as a sibling, just before the target |
| afterend | Insert as a sibling, just after the target |
| delete | Remove the target (ignore the response body) |
| none | Do not swap at all (useful with side-effects) |
The swap is more configurable than it looks◆◆◆◆◆go deeper +
hx-swap takes modifiers after the strategy. A few that matter in real apps:
hx-swap="innerHTML swap:200ms"waits 200ms before swapping, giving you a window to run a CSS exit transition on the old content.hx-swap="innerHTML settle:1s"controls the "settle" phase where HTMX briefly keeps both old and new classes around so CSS transitions can animate the new content in.hx-swap="beforeend scroll:bottom"scrolls the target after swapping — handy for chat logs.hx-swap="innerHTML focus-scroll:false"stops HTMX from auto-scrolling to a newly focused element.
The transition support hooks into the browser's native View Transitions API when available, so you can get cross-fade animations between server states with a single transition:true modifier.
The same feature, two philosophies
To feel why people reach for HTMX, compare how you'd build a trivial "click a star to favorite a post" toggle in two paradigms. Both are legitimate. They make very different bets about where your application's logic and state should live.
The browser holds state. The server is a data API. You serialize, fetch JSON, mutate client state, and re-render.
async function toggleFavorite(postId) {
const res = await fetch(`/api/posts/${postId}/favorite`, {
method: "POST",
headers: { "Content-Type": "application/json" },
});
const data = await res.json(); // { favorited: true, count: 42 }
setFavorited(data.favorited); // update client state
setCount(data.count); // re-render via framework
}
You also wrote: a component, two pieces of useState, a JSON contract, and matching render logic. The server and client both "know" what a favorited post looks like.
The HTMX version has less code and one source of truth, at the cost of a network round-trip per interaction and a server that must render HTML fragments. The SPA version feels instant and works offline-ish, at the cost of duplicated rendering logic and a pile of client state to keep honest. Neither is correct; they are different trades.
Check yourself
In the hypermedia approach, what does the server send back after the favorite button is clicked?
The big idea hiding underneath: HATEOAS
HTMX did not invent its philosophy. It is reviving the original architectural vision of the web, an idea with an unwieldy acronym: HATEOAS — Hypermedia As The Engine Of Application State.
The claim is this: the server should send the client not just data, but data plus the available actions, encoded right into the response. A favorited post comes back as a button that knows how to un-favorite it. The client doesn't need to know the rules of your application in advance — the HTML it receives tells it what is possible next.
Why HATEOAS fell out of fashion — and why HTMX brings it back◆◆◆◆◆go deeper +
For roughly a decade, "REST API" came to mean "JSON over HTTP with nice URLs," which is not what Roy Fielding's 2000 dissertation meant by REST at all. True REST requires the hypermedia constraint: responses carry their own controls. Almost no JSON API does this, because JSON has no native, agreed-upon way to express "here is a link you can follow" or "here is a form you can submit." Every team reinvents it, badly, and clients end up hard-coding the API's rules anyway.
HTML, by contrast, has had hypermedia controls since birth: <a href> is a followable link, <form> is a submittable action. The browser is a fully general hypermedia client that already knows how to render and execute these controls. HTMX's insight is that the problem was never HTML — it was that HTML's hypermedia controls were too restricted to build modern UIs. Fix the restrictions (the four from our table) rather than abandoning hypermedia for JSON, and HATEOAS becomes practical again.
The tradeoff: your client is now "dumb" by design. It cannot make decisions the server didn't encode. For applications where the server is the authority anyway — most CRUD apps, dashboards, admin panels, content sites — that's a feature. For offline-first apps, real-time collaborative editors, or anything with heavy client-side state, the hypermedia model fights you.
A few moves that make it feel magical
Three attributes turn HTMX from "ajax with extra steps" into something that feels like a small superpower.
hx-trigger — request on anything. Live search is the canonical demo: fire a search request as the user types, but debounced, and only when the value actually changed.
<input type="search" name="q"
hx-get="/search"
hx-trigger="keyup changed delay:400ms"
hx-target="#results">
<div id="results"></div>
hx-swap-oob — update somewhere else too. A single response can swap into its target and patch unrelated parts of the page "out of band." Add a comment, and the response can both append the comment and update the "3 comments" counter in the header, in one round trip.
<!-- server response -->
<li>The new comment</li>
<span id="comment-count" hx-swap-oob="true">4 comments</span>
hx-boost — progressive enhancement for free. Put hx-boost="true" on a <body> or a nav, and HTMX upgrades ordinary <a> and <form> elements to swap the page body via ajax instead of doing full reloads. If JavaScript is disabled, the links still work as plain links. The app degrades gracefully to the 1993 web.
When NOT to reach for HTMX
An honest explainer has to draw this line, because HTMX's fans are sometimes evangelical and the model has real limits.
HTMX shines for server-authoritative, page-shaped applications: content sites, dashboards, admin tools, forms-heavy line-of-business apps, anything you'd have happily built with Rails or Django and server-rendered templates. It struggles — or at least stops being the easy choice — when:
- State lives mostly on the client. A spreadsheet, a drawing canvas, a game. Round-tripping every keystroke to the server is the wrong shape.
- You need rich offline behavior. No server, no swaps.
- Interactions must feel zero-latency under poor networks. Every action is a request; a slow connection is felt directly. (You can mask it with optimistic UI, but now you're adding the client logic HTMX let you avoid.)
- You already have a heavy client framework doing real work. HTMX is not trying to replace a genuinely interactive React app; it's questioning whether your app needed to be one.
Check yourself
Which app is the WORST fit for a pure HTMX approach?
The size argument
Part of HTMX's appeal is sheer leanness. It ships as a single dependency-free file of roughly 14 kB gzipped, and the project claims teams have cut total codebase size dramatically by deleting client-state machinery. The chart below puts the library weight in rough perspective — treat the "typical SPA app bundle" figure as illustrative, since real apps vary enormously.
The deeper point isn't the kilobytes — it's that the code you don't write is the real saving. No client router, no state store, no JSON serializers, no duplicate rendering logic. The HTML you'd have written on the server anyway becomes the whole interface.
A short history
HTMX is older than it looks — it's the matured descendant of a library called intercooler.js, carrying the same idea into a dependency-free era.
2013
intercooler.js
Carson Gross releases a jQuery plugin adding ajax attributes to HTML — the conceptual ancestor of HTMX.
Nov 2020
htmx 1.0
A rewrite with zero dependencies. jQuery dropped; the four generalizations crystallize into hx-* attributes.
2023
GitHub Accelerator
htmx joins the first cohort, bringing funding and mainstream attention to the hypermedia revival.
Jun 2024
htmx 2.0
Drops Internet Explorer support, tightens defaults to follow HTTP specs (e.g. DELETE now uses query params), moves extensions to their own repos.
Apr 2026
htmx 2.0.9
Steady, deliberately small maintenance releases — the project treats 'being finished' as a goal, not a failure.
Where this leaves you
HTMX is best understood not as "a way to avoid JavaScript" but as a bet about where complexity belongs. It says: the server already knows the truth and already knows how to render it; let the network carry HTML, let the browser do what it was built to do, and stop rebuilding half of that machinery in the client.
That bet pays off beautifully for a huge class of applications and fights you for others. The skill isn't learning the attributes — there are maybe a dozen that matter, and you now know most of them. The skill is recognizing which shape your application actually is.
An open question to chew on: if the browser is already a complete, general-purpose hypermedia client, how much of what we call "modern frontend development" is solving problems we created by walking away from hypermedia in the first place — and how much is solving problems hypermedia genuinely can't?
Check yourself
What is the single best summary of HTMX's core idea?