htmx lets you build interactive, dynamic UIs using plain HTML attributes instead of a client-side JavaScript framework — you get AJAX requests, WebSocket connections, and partial page swaps by adding attributes like hx-get and hx-swap directly on your markup, and the server responds with HTML fragments instead of JSON. htmx 2.0 kept this model intact while dropping IE11 support and cleaning up internals; the core mental model that made htmx useful hasn't changed.
The core model: HTML is the protocol
Traditional SPA frameworks treat the server as a JSON API and the client as the sole owner of the DOM. htmx inverts this: the server renders HTML, and the client's job is just to swap fragments of that HTML into the page in response to user actions. A button that used to trigger a full page navigation becomes a button with hx-get="/items/42" and hx-target="#detail-panel" — clicking it fires a GET request, and whatever HTML the server returns replaces the contents of #detail-panel. No client-side templating, no state synchronization between a JS model and the DOM, because the DOM never stops being the source of truth.
<!-- Search-as-you-type: fires on keyup, debounced,
replaces the results div with the server's HTML fragment -->
<input type="text" name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results"
hx-swap="innerHTML">
<div id="results"></div>
<!-- Delete a row and remove it from the DOM on success -->
<button hx-delete="/items/42"
hx-target="closest tr"
hx-swap="outerHTML swap:200ms"
hx-confirm="Delete this item?">
Delete
</button>
hx-swap and how partial rendering actually works
hx-swap controls exactly how the returned HTML is inserted: innerHTML (default) replaces the target's contents, outerHTML replaces the target element itself, and modifiers like beforeend or afterbegin insert relative to the target for patterns like infinite-scroll or prepending new chat messages. This means your server-side templates need to be able to render fragments, not just full pages — most people handle this by having the same template partials render both the full page (on initial load) and the fragment (on htmx requests), which is straightforward in Laravel Blade, Django templates, or Rails partials, all of which already support this kind of composition.
Triggers beyond click
hx-trigger is what makes htmx feel closer to a full framework than a jQuery-AJAX replacement — you can trigger requests on any DOM event, with modifiers for debouncing (delay:300ms), firing only on real changes (changed), or triggering on a fixed interval (every 2s) for polling. This last one is a genuinely useful pattern for near-real-time UI without WebSockets: a div with hx-get="/status" hx-trigger="every 2s" polls the endpoint and swaps in updated content, which is enough for a lot of dashboards and progress indicators without the operational complexity of a persistent connection.
Adding hx-boost="true" to a container intercepts clicks on links and form submits inside it, turning them into AJAX requests that swap the body instead of a full page reload — you get faster perceived navigation on an otherwise conventional server-rendered site, without touching your routing or backend rendering logic.
Where htmx fits, and where it doesn't
htmx is a strong fit for CRUD-heavy, server-rendered applications — admin panels, internal tools, content sites with pockets of interactivity — where the alternative would be either full page reloads or standing up a separate SPA just to avoid them. It's a poor fit for applications with heavy client-side state that has to survive independent of the server (a complex drawing tool, an offline-capable app, a page with deeply interdependent widgets that need to react to each other's state instantly) — that's genuinely React/Vue territory, because htmx has no client-side state model of its own beyond the DOM.
Because htmx requests hit real server routes, they need the same auth checks, CSRF protection, and input validation as any other endpoint — htmx doesn't create a new trust boundary, it just changes the response format from a full page to a fragment. It's easy to forget CSRF tokens on `hx-post` forms if you're used to a SPA's fetch wrapper handling that automatically.
Pairing with a backend framework
htmx pairs naturally with any server-rendered framework — Laravel with Blade, Rails, Django, or plain PHP — because the only requirement is that the server can return an HTML fragment for a given route. There's no client build step, no bundler config, and no API layer to design solely for the frontend; the same routes and controllers that render your pages can render the fragments htmx swaps in, often with the same view partial reused in both contexts.
| Attribute | Purpose |
|---|---|
hx-get / hx-post / hx-delete | Fires an HTTP request of that method to the given URL |
hx-target | Which element receives the response (defaults to the element itself) |
hx-swap | How the response is inserted: innerHTML, outerHTML, beforeend, etc. |
hx-trigger | What DOM event fires the request, with modifiers like delay and every |
hx-boost | Converts normal links/forms into AJAX requests automatically |
Wrapping up
htmx's value is in what it lets you avoid: no separate frontend build, no client-side state synchronization, no JSON API designed purely to feed a SPA. For server-rendered, CRUD-shaped applications it removes a lot of accidental complexity; for apps with genuinely complex client-side state, a JS framework is still the right tool. Know which shape your app is before picking.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.