◆◆◆◆◆claude-opus-4-82 people reached

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 elementhx-get, hx-post
trigger on click / submittrigger on ANY DOM eventhx-trigger
use GET and POSTuse GET, POST, PUT, PATCH, DELETEhx-put, hx-patch, hx-delete
replace the whole pagereplace ANY element you targethx-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. 1

    Trigger fires

    The button's default trigger for a non-form element is click. HTMX is listening. (We could change this with hx-trigger="mouseenter" or keyup changed delay:500ms and so on.)

  2. 2

    Request goes out

    HTMX issues GET /comments?page=2 in the background via fetch. It also adds request headers like HX-Request: true, so your server can tell an HTMX call apart from a full-page navigation and respond accordingly.

  3. 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. 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. 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-swapWhat it does
innerHTMLReplace the target's contents (the default)
outerHTMLReplace the target element itself, tag and all
beforeendAppend inside, after the last child
afterbeginInsert inside, before the first child
beforebeginInsert as a sibling, just before the target
afterendInsert as a sibling, just after the target
deleteRemove the target (ignore the response body)
noneDo 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: HATEOASHypermedia 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.

050100150200250300htmxreact + react-domtypical SPA app bundleLIBRARY / BUNDLEAPPROX. KB GZIPPED
minified + gzipped size

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.

  1. 2013

    intercooler.js

    Carson Gross releases a jQuery plugin adding ajax attributes to HTML — the conceptual ancestor of HTMX.

  2. Nov 2020

    htmx 1.0

    A rewrite with zero dependencies. jQuery dropped; the four generalizations crystallize into hx-* attributes.

  3. 2023

    GitHub Accelerator

    htmx joins the first cohort, bringing funding and mainstream attention to the hypermedia revival.

  4. 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.

  5. 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?

Generated with claude-opus-4-8 · curated by Joe · CC BY-SA 4.0 · v1

Discussion 0 comments — open a section's margin marker, or select its text, to comment in place

Nothing here yet.

0/2000