· alpinejs / htmx / javascript

Alpine.js vs HTMX — JS sprinkles or server fragments?

Alpine.js owns client-side UI state; HTMX owns server-driven HTML swaps. Use both together on any SSR stack — or pick one when scope is narrow.

By

1,333 words · 7 min read

Alpine.js and HTMX solve different problems. Pick Alpine.js when you need reactive UI state without a build step — dropdowns, modals, accordions, client-side filtering. Pick HTMX when you want the server to drive page updates — CRUD flows, live search, inline editing, any pattern where returning HTML from the server is simpler than returning JSON and re-rendering it client-side. Use both on the same page when you need both.

Who this is for

Backend developers on server-rendered stacks (Laravel, Django, Rails, Astro) who want richer UIs without adopting React or Vue. If you are already running a full SPA, neither tool is the right question.

What we tested

Alpine.js v3.15.12 (released 2026-04-30) — 31.6k GitHub stars, ~548k weekly npm downloads, ~15 KB min+gzip, MIT. HTMX v2.0.9 (latest v2 stable, released 2026-04-20) — 48.1k GitHub stars, ~168k weekly npm downloads, ~14 KB min+gzip, BSD-2-Clause. Combined payload: ~29 KB min+gzip. Both tools tested against an Astro SSR project on Cloudflare Workers.

Mental models

These tools attack from opposite directions.

Alpine.js is Vue directives without a build step. State lives in the browser. You declare a reactive scope with x-data, bind DOM attributes with x-bind, respond to events with x-on, and toggle visibility with x-show/x-if. The browser runs it all; the server never sees it.

<!-- Alpine.js: client-state dropdown -->
<div x-data="{ open: false }">
  <button x-on:click="open = !open">Menu</button>
  <ul x-show="open">
    <li><a href="/pricing">Pricing</a></li>
    <li><a href="/docs">Docs</a></li>
  </ul>
</div>

HTMX is hypermedia extended. You add attributes like hx-get, hx-post, and hx-swap to existing HTML elements. When the user interacts, HTMX fires a request to the server and swaps the response HTML into the page. There is no client state model — the server owns state, and the browser is a thin render layer.

<!-- HTMX: server-driven search -->
<input
  type="search"
  name="q"
  hx-get="/search"
  hx-trigger="input changed delay:300ms"
  hx-target="#results"
  placeholder="Search tools…"
/>
<div id="results"></div>

The mental model split is the most useful thing to internalize before reading specs or benchmarks.

Alpine.js vs HTMX: at a glance

Alpine.jsHTMX
Bundle (min+gzip)~15 KB~14 KB
State locationBrowserServer
Primary directivex-data / x-bindhx-get / hx-post / hx-swap
Build step requiredNoNo
JSON API neededNoNo
SSR compatibleAny stackAny stack
Learning curveLow — Vue devs: immediateLow — HTML-fluent devs: immediate
GitHub stars31.6k48.1k
Weekly npm downloads~548k~168k
LicenseMITBSD-2-Clause
EcosystemPowers Tailwind UI, Laravel LivewireParis 2024 Olympics; Rails, Django, Laravel

Code patterns

Alpine.js: the right tool for a modal. No server round-trip needed.

<div x-data="{ showModal: false }">
  <button x-on:click="showModal = true">Open modal</button>

  <div
    x-show="showModal"
    x-transition
    class="fixed inset-0 bg-black/50 flex items-center justify-center"
  >
    <div class="bg-white rounded-lg p-6 max-w-md w-full">
      <h2 class="text-xl font-bold mb-4">Confirm action</h2>
      <p class="mb-4">Are you sure you want to proceed?</p>
      <div class="flex gap-2">
        <button x-on:click="showModal = false" class="btn-primary">Confirm</button>
        <button x-on:click="showModal = false" class="btn-secondary">Cancel</button>
      </div>
    </div>
  </div>
</div>

HTMX: the right tool for a search that hits the database. The server returns a rendered <ul> fragment; HTMX swaps it in.

<!-- Client-side markup -->
<input
  type="search"
  name="q"
  hx-get="/tools/search"
  hx-trigger="input changed delay:300ms, search"
  hx-target="#search-results"
  hx-indicator="#spinner"
  placeholder="Search tools…"
/>
<span id="spinner" class="htmx-indicator">Searching…</span>
<ul id="search-results"></ul>
# Server-side: Django view returning an HTML fragment
def tool_search(request):
    q = request.GET.get("q", "")
    tools = Tool.objects.filter(name__icontains=q)[:10]
    return render(request, "partials/tool_list.html", {"tools": tools})

Form POST with partial update

HTMX: posts the form, swaps the response into #feedback without a full page reload.

<form
  hx-post="/tools/1/rate"
  hx-target="#feedback"
  hx-swap="outerHTML"
>
  <input type="hidden" name="tool_id" value="1" />
  <select name="rating">
    <option value="5">★★★★★</option>
    <option value="4">★★★★</option>
    <option value="3">★★★</option>
  </select>
  <button type="submit">Submit rating</button>
</form>
<div id="feedback"></div>

The server returns the replacement <div id="feedback"> with the success or error message. One attribute does what used to require a fetch + DOM write.

Framework homes

Both tools work on any SSR stack, but adoption concentrates where they fit the workflow best.

HTMX is natural for: Rails (Hotwire alternatives), Django (class-based views returning partials), Laravel (Livewire and Blade partials), Astro (server endpoints). These stacks already return HTML from the server — HTMX is a thin wire between that output and the DOM swap.

Alpine.js is natural for: any SSR stack, but especially Laravel (first-class support in the ecosystem), Astro (drops into .astro files without a framework slot), Tailwind UI (all interactive components use Alpine). If you are already on Tailwind UI, Alpine is a forced choice — the components ship with Alpine markup.

Combining them

The recommended HTML-first stack is: server renders with your framework → HTMX moves data, Alpine manages UI state → Tailwind styles everything. Combined bundle: ~29 KB.

A working pattern:

<!-- HTMX loads the list; Alpine tracks the selected item client-side -->
<div x-data="{ selected: null }">
  <button
    hx-get="/tools"
    hx-target="#tool-list"
    hx-swap="innerHTML"
    x-on:htmx:after-request="selected = null"
  >
    Refresh list
  </button>
  <ul id="tool-list">
    <!-- HTMX swaps rendered items here -->
  </ul>
  <template x-if="selected">
    <div class="detail-panel" x-text="selected.name"></div>
  </template>
</div>

The caveat: when HTMX swaps a DOM node, Alpine state attached to the replaced nodes is destroyed. If HTMX replaces #tool-list and items inside had x-data scopes, those scopes are gone. Solutions:

  1. Attach Alpine state to a container HTMX never replaces (the parent div above).
  2. Use hx-swap="morphdom" (with the morphdom extension) — morphdom diffs the DOM instead of replacing it wholesale, preserving Alpine scopes on unchanged nodes.
  3. For HTMX 2.x: use hx-swap="outerHTML transition:true" with the View Transitions API for smoother swaps that also preserve more state.

The Alpine + HTMX combination is a solved problem, not a warning. One rule covers the gap.

Verdict

Pick Alpine.js if:

  • You need client-side UI state: toggles, modals, accordions, tabs, form validation feedback.
  • You are on a static site (Astro, Eleventy, Jekyll) and need interactivity without a JS framework.
  • You use Tailwind UI — Alpine is part of the package.
  • Your backend devs want to write HTML, not JSX.

Pick HTMX if:

  • You want the server to own state and HTML generation.
  • You are replacing JSON API + client-side fetch + DOM write patterns.
  • Your stack is Laravel, Django, Rails, or any framework that renders HTML well.
  • You are building CRUD, server-side search, pagination, or inline editing.

Use both if:

  • You have server-driven data flows (HTMX) AND client UI state (Alpine) on the same page — common on anything beyond a pure content site.
  • You are building on Cloudflare Workers or Pages and want minimal JS overhead — ~29 KB combined is as lean as you get while still covering both patterns.

Caveats

npm download numbers are not user counts. Alpine’s ~548k/week vs HTMX’s ~168k/week reflects automation, CI pulls, and Tailwind UI downloads more than direct adoption comparison. State of JS 2024 shows Alpine at 3% usage at work vs HTMX at 2% — closer than npm suggests.

HTMX v4.0.0-beta4 (published 2026-05-22) is the most recently published release on GitHub. Despite the “beta4” tag name, GitHub’s API marks it prerelease: false — the project does not flag it as a pre-release. v2.0.9 is the latest in the v2 stable line (released 2026-04-20). Before upgrading to v4 in production, check HTMX’s own release notes for the project’s stability assessment.

The article contains affiliate links to Cloudflare Pages/Workers. The verdict above would be identical without that relationship.

References