· alpinejs / htmx / javascript

Alpine.js vs HTMX — rắc gia vị hay trả về fragment?

Alpine.js quản lý UI state phía client; HTMX điều khiển swap HTML từ server. Dùng cả hai trên bất kỳ stack SSR nào — hoặc chọn một khi phạm vi đủ hẹp.

Bởi

1.560 từ · 8 phút đọc

Alpine.js và HTMX giải quyết hai vấn đề khác nhau. Chọn Alpine.js khi bạn cần reactive UI state mà không cần build step — dropdown, modal, accordion, lọc phía client. Chọn HTMX khi bạn muốn server điều khiển việc cập nhật trang — CRUD, live search, inline editing, bất kỳ pattern nào mà trả HTML từ server đơn giản hơn là trả JSON rồi render lại phía client. Dùng cả hai trên cùng một trang khi bạn cần cả hai.

Bài này dành cho ai

Lập trình viên backend trên các stack server-rendered (Laravel, Django, Rails, Astro) muốn giao diện phong phú hơn mà không phải nhập React hay Vue. Nếu bạn đang chạy một SPA đầy đủ, cả hai công cụ này đều không phải câu trả lời đúng.

Chúng tôi đã thử nghiệm gì

Alpine.js v3.15.12 (phát hành 2026-04-30) — 31.6k GitHub stars, ~548k lượt tải npm mỗi tuần, ~15 KB min+gzip, MIT. HTMX v2.0.9 (latest v2 stable, phát hành 2026-04-20) — 48.1k GitHub stars, ~168k lượt tải npm mỗi tuần, ~14 KB min+gzip, BSD-2-Clause. Payload kết hợp: ~29 KB min+gzip. Cả hai công cụ được thử nghiệm với một dự án Astro SSR trên Cloudflare Workers.

Mental model

Hai công cụ tiếp cận từ hai hướng ngược nhau.

Alpine.js giống như Vue directives nhưng không cần build step. State tồn tại trong trình duyệt. Bạn khai báo một reactive scope bằng x-data, bind thuộc tính DOM bằng x-bind, phản ứng với sự kiện bằng x-on, và toggle hiển thị bằng x-show/x-if. Trình duyệt chạy tất cả; server không bao giờ thấy nó.

<!-- Alpine.js: dropdown với client-state -->
<div x-data="{ open: false }">
  <button x-on:click="open = !open">Menu</button>
  <ul x-show="open">
    <li><a href="/pricing">Bảng giá</a></li>
    <li><a href="/docs">Tài liệu</a></li>
  </ul>
</div>

HTMX là hypermedia được mở rộng. Bạn thêm các thuộc tính như hx-get, hx-post, và hx-swap vào các phần tử HTML hiện có. Khi người dùng tương tác, HTMX gửi request đến server và swap HTML phản hồi vào trang. Không có client state model — server sở hữu state, và trình duyệt chỉ là lớp render mỏng.

<!-- HTMX: tìm kiếm điều khiển bởi server -->
<input
  type="search"
  name="q"
  hx-get="/search"
  hx-trigger="input changed delay:300ms"
  hx-target="#results"
  placeholder="Tìm công cụ…"
/>
<div id="results"></div>

Sự phân tách mental model này là điều hữu ích nhất cần nắm trước khi đọc spec hay benchmark.

Alpine.js vs HTMX: bảng so sánh

Alpine.jsHTMX
Bundle (min+gzip)~15 KB~14 KB
Vị trí StateTrình duyệtServer
Directive chínhx-data / x-bindhx-get / hx-post / hx-swap
Cần build stepKhôngKhông
Cần JSON APIKhôngKhông
Tương thích SSRMọi stackMọi stack
Độ khó họcThấp — dev Vue: ngay lập tứcThấp — dev quen HTML: ngay lập tức
GitHub stars31.6k48.1k
Tải npm mỗi tuần~548k~168k
Giấy phépMITBSD-2-Clause
Hệ sinh tháiPowers Tailwind UI, Laravel LivewireParis 2024 Olympics; Rails, Django, Laravel

Các pattern code

Toggle modal

Alpine.js: đây là công cụ phù hợp cho modal. Không cần round-trip server.

<div x-data="{ showModal: false }">
  <button x-on:click="showModal = true">Mở 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">Xác nhận hành động</h2>
      <p class="mb-4">Bạn có chắc chắn muốn tiếp tục không?</p>
      <div class="flex gap-2">
        <button x-on:click="showModal = false" class="btn-primary">Xác nhận</button>
        <button x-on:click="showModal = false" class="btn-secondary">Huỷ</button>
      </div>
    </div>
  </div>
</div>

HTMX: công cụ phù hợp cho tìm kiếm cần truy vấn database. Server trả về một fragment <ul> đã render; HTMX swap vào.

<!-- Markup phía client -->
<input
  type="search"
  name="q"
  hx-get="/tools/search"
  hx-trigger="input changed delay:300ms, search"
  hx-target="#search-results"
  hx-indicator="#spinner"
  placeholder="Tìm công cụ…"
/>
<span id="spinner" class="htmx-indicator">Đang tìm…</span>
<ul id="search-results"></ul>
# Phía server: Django view trả về 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 với cập nhật từng phần

HTMX: post form, swap phản hồi vào #feedback mà không cần reload toàn trang.

<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">Gửi đánh giá</button>
</form>
<div id="feedback"></div>

Server trả về <div id="feedback"> thay thế với thông báo thành công hay lỗi. Một thuộc tính làm được điều mà trước đây cần fetch + DOM write.

Framework phù hợp

Cả hai công cụ đều hoạt động trên mọi stack SSR, nhưng mức độ phổ biến tập trung ở nơi chúng phù hợp nhất với workflow.

HTMX phù hợp nhất với: Rails (thay thế Hotwire), Django (class-based view trả về partial), Laravel (Livewire và Blade partial), Astro (server endpoint). Các stack này đã trả HTML từ server — HTMX chỉ là một dây dẫn mỏng giữa output đó và DOM swap.

Alpine.js phù hợp nhất với: mọi stack SSR, nhưng đặc biệt là Laravel (hỗ trợ first-class trong hệ sinh thái), Astro (tích hợp vào file .astro không cần framework slot), Tailwind UI (mọi component tương tác đều dùng Alpine). Nếu bạn đang dùng Tailwind UI, Alpine là lựa chọn bắt buộc — các component đi kèm markup Alpine.

Kết hợp cả hai

Stack HTML-first được khuyến nghị là: server render với framework của bạn → HTMX di chuyển dữ liệu, Alpine quản lý UI state → Tailwind style mọi thứ. Bundle kết hợp: ~29 KB.

Một pattern hoạt động thực tế:

<!-- HTMX tải danh sách; Alpine theo dõi item đã chọn phía client -->
<div x-data="{ selected: null }">
  <button
    hx-get="/tools"
    hx-target="#tool-list"
    hx-swap="innerHTML"
    x-on:htmx:after-request="selected = null"
  >
    Làm mới danh sách
  </button>
  <ul id="tool-list">
    <!-- HTMX swap các item đã render vào đây -->
  </ul>
  <template x-if="selected">
    <div class="detail-panel" x-text="selected.name"></div>
  </template>
</div>

Lưu ý quan trọng: khi HTMX swap một DOM node, Alpine state gắn với các node bị thay thế sẽ bị mất. Nếu HTMX thay thế #tool-list và các item bên trong có scope x-data, các scope đó biến mất. Giải pháp:

  1. Gắn Alpine state vào container mà HTMX không bao giờ thay thế (div cha ở trên).
  2. Dùng hx-swap="morphdom" (với morphdom extension) — morphdom diff DOM thay vì thay thế toàn bộ, giữ nguyên Alpine scope trên các node không thay đổi.
  3. Với HTMX 2.x: dùng hx-swap="outerHTML transition:true" với View Transitions API để swap mượt hơn, cũng giữ được nhiều state hơn.

Sự kết hợp Alpine + HTMX là bài toán đã có lời giải, không phải cảnh báo. Chỉ cần nắm một quy tắc đó.

Kết luận

Chọn Alpine.js nếu:

  • Bạn cần client-side UI state: toggle, modal, accordion, tab, feedback validate form.
  • Bạn đang dùng static site (Astro, Eleventy, Jekyll) và cần tương tác mà không cần JS framework.
  • Bạn dùng Tailwind UI — Alpine là một phần của gói.
  • Dev backend muốn viết HTML, không phải JSX.

Chọn HTMX nếu:

  • Bạn muốn server sở hữu state và sinh HTML.
  • Bạn đang thay thế các pattern JSON API + client-side fetch + DOM write.
  • Stack của bạn là Laravel, Django, Rails, hoặc bất kỳ framework nào render HTML tốt.
  • Bạn đang xây CRUD, server-side search, pagination, hoặc inline editing.

Dùng cả hai nếu:

  • Bạn có server-driven data flow (HTMX) VÀ client UI state (Alpine) trên cùng một trang — phổ biến với bất kỳ thứ gì phức tạp hơn pure content site.
  • Bạn đang build trên Cloudflare Workers hoặc Pages và muốn JS overhead tối thiểu — ~29 KB kết hợp là nhỏ nhất có thể trong khi vẫn xử lý được cả hai pattern.

Lưu ý

Số liệu tải npm không phải số người dùng. Alpine ~548k/tuần so với HTMX ~168k/tuần phản ánh automation, CI pull, và lượt tải Tailwind UI nhiều hơn là so sánh trực tiếp mức độ phổ biến. State of JS 2024 cho thấy Alpine ở 3% usage tại nơi làm việc so với HTMX ở 2% — gần hơn những gì npm gợi ý.

HTMX v4.0.0-beta4 (phát hành 2026-05-22) là bản phát hành mới nhất trên GitHub. Dù tên tag có “beta4”, GitHub API đánh dấu nó là prerelease: false — dự án không coi đây là bản pre-release. v2.0.9 là bản mới nhất trong dòng v2 stable (phát hành 2026-04-20). Trước khi nâng cấp lên v4 trong production, hãy kiểm tra release notes của HTMX để đánh giá mức độ ổn định từ phía dự án.

Bài viết chứa liên kết affiliate đến Cloudflare Pages/Workers. Kết luận ở trên sẽ giống hệt nếu không có mối quan hệ đó.

Tham khảo