HTMX vs Alpine.js: Lightweight Interactivity Compared
Two libraries, both under 15KB, both driven by HTML attributes, both rejecting the SPA status quo. They sound interchangeable — until you look at what they actually do.
HTMX makes HTML elements issue HTTP requests and swap server-rendered fragments into the page. Alpine.js adds reactive client-side state to HTML without a build step. One talks to your server. The other manages your UI. They solve entirely different problems, and the teams shipping the fastest lightweight apps in 2026 are using both.
We compared them using real data from PkgPulse. Here's where each one fits — and why the "vs" framing misses the point.
TL;DR
HTMX replaces AJAX calls. Alpine.js replaces jQuery UI logic. Use HTMX when you need to fetch data from the server without a full page reload. Use Alpine.js when you need client-side interactivity — dropdowns, toggles, modals — without touching the server. Use both together when your app needs server data and local UI state. Neither replaces React or Vue for complex single-page applications.
Key Takeaways
- HTMX is server-driven. It extends HTML to make HTTP requests and swap HTML fragments. Your server is the source of truth, returning rendered HTML — not JSON.
- Alpine.js is client-driven. It adds reactive state, conditional rendering, and event handling directly in your HTML markup. No server round-trip needed for UI interactions.
- They're complementary, not competing. HTMX fetches and swaps server content. Alpine.js manages local UI state. Using both is a documented, well-supported pattern.
- Neither requires a build step. Add a
<script>tag and start writing HTML attributes. No npm, no webpack, no Vite configuration. - The "HTML-first" stack is real. Django/Rails/Go + HTMX + Alpine.js + Tailwind CSS is a production-proven combination shipping fast, maintainable applications.
- Complex SPAs still need SPA frameworks. Real-time collaboration, offline-first, drag-and-drop builders, and canvas editors exceed what either library is designed for.
At a Glance
| Metric | HTMX | Alpine.js |
|---|---|---|
| Bundle Size (min+gzip) | ~14KB | ~15KB |
| Primary Role | Server-driven content updates | Client-side reactivity |
| Attribute Prefix | hx- | x- |
| Data Source | Server (HTML fragments) | Client (inline state) |
| Build Step Required | No | No |
| Server Round-trip | Yes, every interaction | No |
| Current Version | 2.x | 3.x |
| Language Required | Any server language | None (HTML + JS expressions) |
| License | BSD 2-Clause | MIT |
See the full live comparison — download trends and health scores — at pkgpulse.com/compare/alpine.js-vs-htmx
How HTMX Works
HTMX extends HTML with attributes that let any element issue HTTP requests and update the DOM with the server's response. The server returns rendered HTML fragments — not JSON. No client-side templating. No JavaScript data transformation. The server decides what the user sees.
<!-- A search input that fetches results from the server on every keystroke -->
<input type="search"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results"
hx-swap="innerHTML"
placeholder="Search users..." />
<div id="results">
<!-- Server-rendered HTML fragments appear here -->
</div>
The hx-get attribute tells HTMX to issue a GET request to /search. The hx-trigger fires on keyup with a 300ms debounce. The hx-target specifies where to insert the response. The hx-swap controls how the content is inserted.
Your server endpoint returns a plain HTML fragment:
<!-- Server response — just HTML, no JSON wrapper -->
<ul>
<li>Alice Johnson — alice@example.com</li>
<li>Alex Morgan — alex@example.com</li>
</ul>
HTMX swaps that fragment into #results. No parsing. No virtual DOM. No state synchronization. The server rendered it; the browser displays it.
Key HTMX attributes:
hx-get,hx-post,hx-put,hx-delete— issue HTTP requestshx-target— which element receives the responsehx-swap— how the response is inserted (innerHTML,outerHTML,beforeend, etc.)hx-trigger— what event triggers the requesthx-indicator— show a loading spinner during the request
HTMX 2.0 added ESM module support, a public htmx.swap() API, and selfRequestsOnly as a security default. It dropped IE support and moved extensions (SSE, WebSockets) to independent repositories.
How Alpine.js Works
Alpine.js adds reactive state and behavior directly in your HTML. Think of it as a modern, declarative replacement for the jQuery patterns we used to write — toggling classes, showing and hiding elements, managing dropdown state — except with reactivity built in.
<!-- A dropdown menu with no server interaction -->
<div x-data="{ open: false }">
<button @click="open = !open" class="btn">
Menu
<span x-show="open">↑</span>
<span x-show="!open">↓</span>
</button>
<ul x-show="open"
x-transition
@click.outside="open = false"
class="dropdown-menu">
<li><a href="/profile">Profile</a></li>
<li><a href="/settings">Settings</a></li>
<li><a href="/logout">Log out</a></li>
</ul>
</div>
The x-data attribute initializes a reactive scope with an open boolean. @click toggles it. x-show conditionally renders elements based on the state. x-transition adds CSS transitions. @click.outside closes the dropdown when clicking elsewhere.
All of this happens in the browser. No server request. No page reload. No JavaScript file to write — the logic lives in the HTML.
Key Alpine.js directives:
x-data— declare reactive state on an elementx-show/x-if— conditional renderingx-bind— dynamically bind attributesx-on/@— listen for eventsx-model— two-way data binding for inputsx-for— loop over arraysx-transition— apply CSS transitionsx-effect— run side effects when dependencies change
Alpine.js also ships with a $store for global state, $refs for DOM access, and $watch for reacting to state changes. It covers the 90% of client-side interactivity that used to require React or Vue — without any of the tooling overhead.
Using Them Together: The Power Combo
Here's where the "vs" framing breaks down. HTMX and Alpine.js solve different problems at different layers. Using them together is not a hack — it's the intended pattern for many applications.
HTMX handles server communication. Load data, submit forms, paginate results, update content from the server.
Alpine.js handles local UI. Toggles, modals, tabs, accordions, client-side filtering, form validation feedback.
<!-- HTMX loads the data. Alpine.js manages the UI around it. -->
<div x-data="{ showFilters: false, selectedTab: 'all' }">
<!-- Alpine.js: toggle filter panel -->
<button @click="showFilters = !showFilters" class="btn-secondary">
<span x-text="showFilters ? 'Hide Filters' : 'Show Filters'"></span>
</button>
<!-- Alpine.js: tab navigation (client-side only) -->
<nav class="tabs">
<button @click="selectedTab = 'all'"
:class="selectedTab === 'all' ? 'tab-active' : ''">All</button>
<button @click="selectedTab = 'active'"
:class="selectedTab === 'active' ? 'tab-active' : ''">Active</button>
<button @click="selectedTab = 'archived'"
:class="selectedTab === 'archived' ? 'tab-active' : ''">Archived</button>
</nav>
<!-- Alpine.js: collapsible filter panel -->
<div x-show="showFilters" x-transition class="filter-panel">
<!-- HTMX: server-driven search within the filter panel -->
<input type="search"
name="q"
hx-get="/users/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#user-list"
hx-include="[name='status']"
placeholder="Search..." />
<select name="status"
hx-get="/users/search"
hx-trigger="change"
hx-target="#user-list">
<option value="">All Statuses</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<!-- HTMX: server-rendered content area -->
<div id="user-list"
hx-get="/users"
hx-trigger="load"
hx-swap="innerHTML">
<!-- Server-rendered user list appears here -->
</div>
</div>
Alpine.js manages the filter panel visibility and tab state — purely client-side, zero server requests. HTMX handles the actual data fetching — search queries, status filtering, initial page load. Each library does what it's best at.
This is the "HTML-first" stack in action: a server framework (Django, Rails, Go, Laravel) renders the initial page, HTMX handles dynamic server interactions, Alpine.js handles local UI behavior, and Tailwind CSS handles styling. No build step. No bundler. No npm dependency tree.
When HTMX Alone Is Enough
If your application is primarily about data — reading it, writing it, filtering it, paginating it — HTMX can handle the interactivity without Alpine.js.
Good fits for HTMX only:
- CRUD applications where every action hits the server (create, read, update, delete)
- Server-side search with results rendered as HTML partials
- Infinite scroll and pagination
- Form submissions with server-side validation
- Admin panels where each action modifies server state
- Content-heavy pages with dynamic sections loaded on demand
In these cases, every meaningful interaction requires a server round-trip anyway. Adding Alpine.js for a few toggles might not be worth the extra 15KB — a simple <details> element or a few lines of vanilla JavaScript can handle what little client-side UI exists.
The test: if you can describe every interaction as "user clicks, server responds with new HTML," HTMX alone is likely sufficient.
When Alpine.js Alone Is Enough
If your application is primarily about client-side presentation — showing, hiding, filtering, and animating content that's already on the page — Alpine.js can handle it without HTMX.
Good fits for Alpine.js only:
- Static sites with interactive UI components (menus, accordions, carousels)
- Documentation pages with collapsible sections and search filtering
- Marketing pages with interactive demos or calculators
- Form UIs with multi-step wizards, conditional fields, and real-time validation
- Any page where the data is already present and just needs to be reorganized or revealed
Alpine.js pairs well with traditional form submissions and full page reloads. If your app already works with standard HTML forms and you only need interactivity for the UI chrome — navigation menus, modal dialogs, toast notifications — Alpine.js is all you need.
The test: if you can describe every interaction as "user clicks, something on the page changes without talking to the server," Alpine.js alone is likely sufficient.
When You Need Both
Most real-world applications land here. They need to fetch data from the server and manage local UI state. That's the sweet spot for HTMX + Alpine.js together.
Signals you need both:
- Your page has server-loaded content (search results, paginated lists, filtered data) and client-side UI components (dropdowns, modals, tabs, tooltips)
- Users interact with forms that submit to the server and the form has conditional fields, inline validation, or multi-step flows
- You need loading states, optimistic UI hints, or transition animations around server-fetched content
- Your app has a sidebar or navigation that toggles client-side, plus a main content area that loads from the server
A dashboard is the canonical example. The sidebar navigation is Alpine.js (toggles, active states, collapse). The main content area is HTMX (load data, filter results, paginate tables). The modal for editing a record uses both — Alpine.js opens and closes it, HTMX loads the form from the server and submits it.
A common pattern: Alpine.js manages the container (show/hide, transitions, tabs), and HTMX manages the content inside it (server-rendered fragments). They don't conflict because they operate at different layers.
When Neither Is Enough
HTMX and Alpine.js are tools for enhancing HTML-driven applications. They are not application frameworks. Some things genuinely require the client-side state management, rendering pipelines, and ecosystem that React, Vue, or Svelte provide.
Reach for a SPA framework when:
- Real-time collaboration — Google Docs-style concurrent editing needs client-side conflict resolution, operational transforms, and persistent WebSocket state that goes beyond HTML swapping.
- Offline-first applications — HTMX requires a server for every interaction. If your app must work without a network connection, you need client-side state persistence and sync logic.
- Complex drag-and-drop — Page builders, Kanban boards, and design tools need fine-grained DOM control, gesture handling, and state management that neither HTMX nor Alpine.js provides.
- Canvas and WebGL — Data visualizations, games, and graphics editors need imperative rendering APIs. Declarative HTML attributes don't apply.
- Large client-side state graphs — If your UI has deeply nested, interdependent state with undo/redo, time-travel debugging, and computed derived state, you need a proper state management system.
This isn't a limitation — it's a design boundary. HTMX and Alpine.js are simple because they don't try to solve these problems.
The Verdict
HTMX and Alpine.js aren't alternatives to each other. They're alternatives to different parts of a SPA framework.
HTMX replaces the data-fetching and rendering layer — fetch() calls, JSON parsing, client-side templating. Your server renders HTML, and HTMX swaps it in. If your app is data-driven and your server already generates HTML, HTMX eliminates the need for a JavaScript rendering layer.
Alpine.js replaces the UI behavior layer — event handling, conditional rendering, reactive state for dropdowns, modals, and toggles. If your interactivity is local to the page and doesn't need server data, Alpine.js eliminates the need for a JavaScript framework.
Together, they cover the interactivity needs of most web applications — at a combined cost of ~29KB. That's less than React alone, with no build step, no bundler, and no npm dependency tree.
The question isn't "HTMX or Alpine.js?" It's "which interactions need the server, and which ones don't?" The first set gets HTMX attributes. The second set gets Alpine.js directives. The result is an application that's fast, simple, and built on HTML that any developer can read.
Compare Alpine.js vs HTMX on PkgPulse →
Frequently Asked Questions
Can I use HTMX and Alpine.js together?
Yes — and many teams do. It's a well-documented pattern. HTMX handles server communication (fetching data, submitting forms, swapping HTML fragments), and Alpine.js handles client-side UI state (dropdowns, modals, tabs, transitions). They use different attribute prefixes (hx- and x-) and operate at different layers, so they don't conflict. The combination is sometimes called the "HTML-first" stack, often paired with Django, Rails, or Go on the backend and Tailwind CSS for styling.
Which one should I learn first?
It depends on your application. If you're adding interactivity to a server-rendered app — loading content without page reloads, handling form submissions, building search interfaces — start with HTMX. If you're adding client-side UI components — dropdowns, modals, tabs, accordions — start with Alpine.js. If your app needs both (most do), start with whichever solves your most immediate problem. Both have minimal learning curves compared to React or Vue.
Do HTMX and Alpine.js replace React?
Not for complex applications. React excels at highly interactive UIs with deep client-side state — real-time collaboration, offline-first apps, design tools, dashboards with complex data flows. HTMX and Alpine.js replace React for the large category of applications that don't need that level of client-side complexity: CRUD apps, content sites, admin panels, and traditional server-rendered web applications that need modern interactivity.
What backend frameworks work best with HTMX + Alpine.js?
Any framework that renders HTML. Django and Rails have the strongest HTMX integration stories, with dedicated libraries (django-htmx, htmx-rails) and active communities. Go (with html/template or Templ), Laravel, Flask, and Phoenix are also popular choices. The key requirement is that your backend returns HTML fragments — not JSON APIs. If your framework already has a template engine, it works with HTMX.
Explore more comparisons: HTMX vs React, React vs Vue, or Astro vs Next.js on PkgPulse.
See the live comparison
View alpine.js vs. htmx on PkgPulse →