htmx
htmx is a small JavaScript library that lets you build interactive web UIs using HTML attributes instead of writing JavaScript. Any element can issue an AJAX request (hx-get, hx-post), and the server responds with an HTML fragment that htmx swaps into the page. There's no client-side state, no virtual DOM, no build step — the server stays the source of truth and sends HTML, the way the web originally worked.
It represents a genuine counter-movement to the React-everywhere default: for CRUD apps, dashboards, and content sites, htmx delivers most of the interactivity of a SPA with a fraction of the complexity, letting backend-focused teams (Django, Rails, Laravel, Flask, Go) add dynamic behavior without adopting a whole frontend ecosystem.
TL;DR
- htmx extends HTML with attributes:
hx-get/hx-post(make a request),hx-target(where to put the response),hx-swap(how),hx-trigger(on what event). - The server returns HTML fragments, not JSON — no client-side rendering, no serialization/deserialization layer.
- The server keeps all the state. The browser holds no application state; the current HTML is the state (the "hypermedia" model).
- It's ~14 KB, has no build step, and drops into any server-rendered stack.
- Best fit: content and CRUD-heavy apps; poor fit: highly stateful client experiences (canvas editors, offline-first, real-time collaborative UIs).
- Pair it with Alpine.js for the small bits of pure-client interactivity (dropdowns, toggles) that don't need a server round-trip.
Quick Example
An active-search box and an inline-editable list — no custom JavaScript:
The mental model: the server renders HTML; htmx wires HTML events to HTTP requests and splices the responses back in.
Core Concepts
The Four Core Attributes
Almost everything is a combination of these:
That reads almost like a sentence: on click, POST to /like/42, put the response inside #like-count.
Hypermedia as the Engine of State (HATEOAS, in practice)
The philosophical core, and the source of htmx's simplicity: the browser holds no application state. In a React app, client state (a useState store, a cache) mirrors server state, and keeping them in sync is most of the work — and most of the bugs. In htmx, there is nothing to sync: the HTML currently on the page is the entire client state, and every interaction fetches fresh HTML from the authority. You delete the whole category of client/server state divergence by not having client state.
The cost is a network round-trip per interaction (mitigated by htmx being fast, responses being small, and no JSON hydration), and needing a responsive server.
Server-Side Fragments
htmx pushes you toward a clean server pattern: templates composed of partials that render either standalone (full page) or as fragments (htmx response). Frameworks lean in:
- Django: template partials,
django-htmxfor request detection - Rails: Turbo is the same philosophy (htmx and Turbo/Hotwire are cousins)
- Laravel / Flask / Go templates: render a partial template per endpoint
- Check the
HX-Requestheader to decide full-page vs fragment; useHX-Triggerresponse headers to fire client events.
The Small-Interactivity Gap: Alpine.js
htmx handles server round-trips; it deliberately doesn't handle purely-local interactivity (toggling a dropdown, a client-side tab). The idiomatic pairing is Alpine.js — a tiny declarative library for exactly those (x-data, x-show, @click). htmx for anything touching the server, Alpine for anything that doesn't — together they cover a huge range with almost no custom JS.
htmx vs a SPA Framework
The honest framing: these optimize for different problems. If your app is fundamentally screens of server data with actions on them — which describes most business software — htmx removes an enormous amount of accidental complexity. If your app is fundamentally a client-side program that happens to run in a browser (Figma, a spreadsheet, a game), you want a real client framework, and htmx would fight you. Many teams use htmx for the app and a component framework for the few genuinely rich surfaces. Astro's island model and htmx share the "HTML-first, JS where needed" instinct.
Common Mistakes
Rebuilding a SPA in Attributes
If you find yourself managing complex state across a dozen hx- calls with hx-vals and hidden inputs, you've hit htmx's ceiling — that surface wanted a client component. Recognize the boundary instead of forcing hypermedia onto a genuinely stateful UI.
Returning JSON
The single most common beginner error: an endpoint returns JSON and nothing swaps usefully. htmx expects HTML. If you need JSON (for a third-party consumer), that's a different endpoint — see REST API Design.
Ignoring the No-JS / History Story
Because htmx swaps fragments, you must handle browser history (hx-push-url), and think about what happens without JavaScript (progressive enhancement — start with working <form>/<a>, enhance with htmx). Skipping this breaks back-buttons and bookmarking.
Chatty Endpoints Without Server Discipline
Every interaction is a request, so a slow server or an N+1 query is felt directly by users. htmx makes server performance a frontend concern — apply caching and query optimization accordingly.
Forgetting Security Basics
Server-rendered fragments must still escape output (XSS) and validate input, and htmx requests need CSRF tokens like any form post (htmx can attach them via hx-headers or a meta tag). The API security rules don't relax because the response is HTML.
FAQ
Is htmx a serious choice or a novelty?
Serious, and increasingly mainstream — it's used in production, and its philosophy (Rails' Hotwire/Turbo, Phoenix LiveView, Laravel Livewire, Astro) reflects a broad industry re-evaluation of SPA-by-default. It won't replace React for React's problems, but for the vast middle of web apps it's a legitimate, simpler default.
Do I still need to know JavaScript?
Less of it. You avoid the framework/build/state-management stack, but you'll still write small amounts of JS (or Alpine.js) for local interactivity, and you must understand HTTP, events, and the swap model. It shifts complexity from client architecture to server rendering — not to zero.
How does htmx compare to Hotwire/Turbo?
Same hypermedia philosophy, different packaging. Turbo (Rails, but framework-agnostic) is more convention-driven and page/frame-oriented; htmx is more granular and attribute-driven, and framework-neutral. Choosing between them is mostly ecosystem fit — see Ruby on Rails for Turbo's home.
Can htmx do real-time updates?
Yes — via hx-trigger="every 2s" polling, or Server-Sent Events / WebSocket extensions that swap fragments as messages arrive. For heavy real-time collaboration, a dedicated client framework is still the better tool.
What's the performance profile?
Excellent first load (tiny JS, server-rendered HTML — strong Core Web Vitals) and per-interaction latency bounded by your server plus one round-trip. It trades the SPA's "instant local, heavy initial load" for "light load, network-bound interactions" — usually a good trade for content and CRUD.
Related Topics
- React — The SPA default htmx pushes back on
- Astro — The same HTML-first instinct, build-time
- Ruby on Rails — Turbo/Hotwire, htmx's cousin
- Django / Flask / Laravel — Natural server backends
- REST API Design — When you do need JSON instead
- Web Performance — Why light-JS pages win
- HTML — The medium htmx doubles down on