· 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 Ethan
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.js | HTMX | |
|---|---|---|
| Bundle (min+gzip) | ~15 KB | ~14 KB |
| Vị trí State | Trình duyệt | Server |
| Directive chính | x-data / x-bind | hx-get / hx-post / hx-swap |
| Cần build step | Không | Không |
| Cần JSON API | Không | Không |
| Tương thích SSR | Mọi stack | Mọi stack |
| Độ khó học | Thấp — dev Vue: ngay lập tức | Thấp — dev quen HTML: ngay lập tức |
| GitHub stars | 31.6k | 48.1k |
| Tải npm mỗi tuần | ~548k | ~168k |
| Giấy phép | MIT | BSD-2-Clause |
| Hệ sinh thái | Powers Tailwind UI, Laravel Livewire | Paris 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>
Live search
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:
- Gắn Alpine state vào container mà HTMX không bao giờ thay thế (div cha ở trên).
- 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. - 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
- Tài liệu Alpine.js
- GitHub Alpine.js — v3.15.12
- Tài liệu HTMX
- GitHub HTMX — v4.0.0-beta4 (phát hành gần nhất, 2026-05-22)
- GitHub HTMX — v2.0.9 (latest v2 stable, 2026-04-20)
- npm trends: Alpine vs HTMX lượt tải mỗi tuần
- State of JS 2024 — front-end frameworks
- HTMX essays — Paris 2024 Olympics case study
- So sánh Alpine AJAX
- PkgPulse — HTMX vs Alpine.js 2026
- HTMX morphdom extension