1. Pengenalan HTMX
HTMX adalah library ringan yang memungkinkan Anda membangun UI dinamis langsung dari HTML, tanpa perlu menulis JavaScript. HTMX memperluas kemampuan HTML standar sehingga elemen-elemen biasa seperti <button>, <div>, atau <form> bisa melakukan permintaan AJAX, mengganti konten halaman, dan berinteraksi dengan server secara real-time.
Filosofi HTMX berakar pada hypermedia — konsep asli web di mana HTML adalah API utama. Alih-alih membangun Single Page Application (SPA) dengan framework JavaScript berat, HTMX mengembalikan pendekatan server-centric yang lebih sederhana.
Mengapa HTMX?
| Keunggulan | Penjelasan | Contoh |
|---|---|---|
| Sederhana | Hanya butuh atribut HTML, tanpa JS | <button hx-get="/data"> |
| Ringan | ~14KB minified, tanpa dependency | Cocok untuk website performa tinggi |
| Kompatibel | Bekerja dengan bahasa backend apapun | Python, Go, PHP, Rust, Node.js |
| Progressive Enhancement | Tetap berfungsi tanpa JavaScript | Graceful degradation alami |
| HATEOAS | Implementasi arsitektur hypermedia REST | Server mengembalikan HTML, bukan JSON |
Instalasi HTMX
<!-- CDN (paling mudah) --> <head> <script src="https://unpkg.com/htmx.org@2.0.4"></script> </head> <!-- Atau via npm --> <!-- npm install htmx.org --> <!-- import 'htmx.org'; -->
<!-- Ketika tombol diklik, isi #result diganti dengan response dari /api/greeting --> <button hx-get="/api/greeting" hx-target="#result" hx-swap="innerHTML"> Dapatkan Salam </button> <div id="result"> <!-- Response dari server akan muncul di sini --> </div>
HTMX tidak menggantikan JavaScript sepenuhnya — untuk interaksi kompleks seperti drag-and-drop, animasi rumit, atau manipulasi data klien, JavaScript tetap diperlukan. HTMX ideal untuk operasi CRUD, navigasi, dan pembaruan UI berbasis server.
2. Atribut Dasar HTMX
HTMX bekerja melalui atribut-atribut spesifik yang ditambahkan ke elemen HTML. Berikut atribut-atribut inti yang harus Anda kuasai:
Atribut Permintaan (Request Attributes)
| Atribut | Fungsi | Contoh |
|---|---|---|
hx-get | Melakukan HTTP GET | hx-get="/api/users" |
hx-post | Melakukan HTTP POST | hx-post="/api/users" |
hx-put | Melakukan HTTP PUT | hx-put="/api/users/1" |
hx-patch | Melakukan HTTP PATCH | hx-patch="/api/users/1" |
hx-delete | Melakukan HTTP DELETE | hx-delete="/api/users/1" |
Atribut Target & Swap
<!-- hx-target: elemen mana yang akan di-update --> <!-- hx-swap: bagaimana konten di-swap --> <!-- Replace innerHTML (default) --> <button hx-get="/data" hx-target="#output" hx-swap="innerHTML"> Load Data </button> <!-- Replace seluruh elemen --> <button hx-get="/data" hx-target="#output" hx-swap="outerHTML"> Load & Replace </button> <!-- Insert sebelum elemen target --> <button hx-get="/item" hx-target="#list" hx-swap="beforebegin"> Tambah di Atas </button> <!-- Insert setelah elemen target --> <button hx-get="/item" hx-target="#list" hx-swap="afterend"> Tambah di Bawah </button> <!-- Swap dengan animasi --> <button hx-get="/data" hx-target="#output" hx-swap="innerHTML settle:500ms"> Load dengan Delay </button> <div id="output"></div> <ul id="list"> <li>Item 1</li> </ul>
Atribut Sinkronisasi & Indikator
<!-- hx-indicator: menampilkan loading spinner saat request berlangsung -->
<button hx-get="/api/slow-data" hx-target="#data" hx-indicator="#spinner">
Load Data Lambat
</button>
<div id="spinner" class="htmx-indicator">
⏳ Loading...
</div>
<div id="data"></div>
<style>
/* Sembunyikan indicator secara default */
.htmx-indicator {
display: none;
}
/* Tampilkan saat HTMX sedang request */
.htmx-request .htmx-indicator {
display: inline-block;
}
.htmx-request.htmx-indicator {
display: inline-block;
}
</style>
3. Triggers & Event Handling
Secara default, HTMX memicu permintaan saat elemen diklik (untuk button/link) atau di-submit (untuk form). Namun Anda bisa mengkustomisasi trigger menggunakan atribut hx-trigger.
<!-- Trigger saat mouseover -->
<div hx-get="/tooltip" hx-trigger="mouseenter" hx-target="#tooltip-box">
Hover saya untuk tooltip
</div>
<!-- Trigger saat pengguna berhenti mengetik (debounce) -->
<input type="text"
hx-get="/api/search"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
placeholder="Cari...">
<!-- Trigger setiap 5 detik (polling) -->
<div hx-get="/api/live-feed" hx-trigger="every 5s" hx-target="#feed">
<div id="feed">Live feed akan update otomatis...</div>
</div>
<!-- Trigger saat elemen terlihat di viewport -->
<div hx-get="/api/more-content" hx-trigger="revealed" hx-target="#infinite-list">
<ul id="infinite-list"></ul>
</div>
<!-- Multiple triggers -->
<input type="text"
hx-get="/api/validate"
hx-trigger="keyup changed delay:300ms, blur"
hx-target="#validation-msg">
<!-- Custom event -->
<div hx-get="/api/data" hx-trigger="myCustomEvent" hx-target="#data">
Data container
</div>
<button onclick="htmx.trigger('[hx-trigger*=myCustomEvent]', 'myCustomEvent')">
Trigger Custom Event
</button>
Modifier Trigger
| Modifier | Fungsi | Contoh |
|---|---|---|
once | Hanya trigger sekali | hx-trigger="click once" |
changed | Hanya jika nilai berubah | hx-trigger="input changed" |
delay:XXms | Tunda sebelum trigger | hx-trigger="keyup delay:500ms" |
throttle:XXms | Batasi frekuensi trigger | hx-trigger="scroll throttle:1s" |
target:selector | Trigger dari elemen lain | hx-trigger="click target:.btn" |
consume | Mencegah propagasi event | hx-trigger="click consume" |
queue:first/last/all | Atur antrian event | hx-trigger="input queue:all" |
4. AJAX dengan HTMX
HTMX mempermudah AJAX request dengan hanya menambah atribut pada elemen HTML. Server mengembalikan fragmen HTML, bukan JSON, yang langsung di-insert ke DOM.
Form Submission
<!-- Form biasa yang di-submit via AJAX --> <form hx-post="/api/contact" hx-target="#form-result" hx-swap="innerHTML"> <label for="name">Nama:</label> <input type="text" id="name" name="name" required> <label for="email">Email:</label> <input type="email" id="email" name="email" required> <label for="message">Pesan:</label> <textarea id="message" name="message" rows="4" required></textarea> <button type="submit">Kirim</button> </form> <div id="form-result"></div>
from flask import Flask, request
app = Flask(__name__)
@app.route('/api/contact', methods=['POST'])
def contact():
name = request.form.get('name')
email = request.form.get('email')
message = request.form.get('message')
# Simpan ke database...
save_contact(name, email, message)
# Kembalikan fragmen HTML (bukan JSON!)
return '''
<div class="success-message">
<h3>✅ Pesan Terkirim!</h3>
<p>Terima kasih, {name}! Kami akan membalas ke {email}.</p>
</div>
'''.format(name=name, email=email)
Headers & Parameters
<!-- hx-vals: mengirim data tambahan dengan setiap request -->
<button hx-post="/api/action"
hx-vals='{"role": "admin", "page": 1}'
hx-target="#result">
Kirim dengan Data Ekstra
</button>
<!-- hx-headers: mengirim custom headers -->
<button hx-get="/api/protected"
hx-headers='{"Authorization": "Bearer mytoken123"}'
hx-target="#data">
Akses Endpoint Terproteksi
</button>
<!-- hx-include: sertakan nilai dari elemen lain -->
<input type="hidden" name="token" value="abc123" id="csrf-token">
<button hx-post="/api/action"
hx-include="#csrf-token"
hx-target="#result">
Kirim dengan CSRF Token
</button>
<!-- hx-params: filter parameter yang dikirim -->
<form hx-post="/api/search" hx-params="*" hx-target="#results">
<input type="text" name="q">
<input type="hidden" name="source" value="web">
<button type="submit">Cari</button>
</form>
Error Handling
<!-- hx-target-*: target berdasarkan status code -->
<form hx-post="/api/users"
hx-target="#success-msg"
hx-target-422="#error-msg"
hx-swap="innerHTML">
<input type="text" name="username" required>
<button type="submit">Daftar</button>
</form>
<div id="success-msg"></div>
<div id="error-msg"></div>
// Menangani response error secara global
document.body.addEventListener('htmx:responseError', function(evt) {
const xhr = evt.detail.xhr;
const status = xhr.status;
switch(status) {
case 404:
alert('Resource tidak ditemukan (404)');
break;
case 422:
console.log('Validation error:', xhr.responseText);
break;
case 500:
alert('Server error. Coba lagi nanti.');
break;
default:
alert('Error: ' + status);
}
});
// Menggunakan htmx:configRequest untuk manipulasi request
document.body.addEventListener('htmx:configRequest', function(evt) {
evt.detail.headers['X-CSRF-Token'] = getCsrfToken();
});
5. HTMX Boosting
HTMX Boosting memungkinkan Anda meng-upgrade seluruh link dan form di halaman agar menggunakan AJAX secara otomatis, tanpa menambah atribut HTMX pada setiap elemen satu per satu.
<!-- Boost link individu -->
<a href="/about" hx-boost="true">Tentang Kami</a>
<!-- Boost seluruh section -->
<main hx-boost="true">
<a href="/page1">Halaman 1</a> <!-- Akan di-load via AJAX -->
<a href="/page2">Halaman 2</a> <!-- Akan di-load via AJAX -->
<a href="/page3">Halaman 3</a> <!-- Akan di-load via AJAX -->
<form action="/submit" method="POST"> <!-- Juga via AJAX -->
<input name="data">
<button type="submit">Submit</button>
</form>
</main>
<!-- Boost seluruh body (semua link & form di halaman) -->
<body hx-boost="true">
...
</body>
<!-- Push URL baru ke browser history -->
<a href="/new-page" hx-get="/new-page" hx-target="#content"
hx-push-url="true">
Navigasi dengan History Push
</a>
<!-- Mengubah URL tanpa push (replace) -->
<div hx-get="/tab-content" hx-trigger="click"
hx-push-url="false" hx-target="#tab-body">
Tab Content
</div>
Ketika menggunakan hx-boost, link eksternal dan anchor links (#) tidak akan terpengaruh. HTMX secara otomatis mengabaikan link yang menargetkan domain berbeda atau link fragment.
6. Server-Sent Events (SSE)
Server-Sent Events (SSE) memungkinkan server mengirim update real-time ke klien. HTMX mendukung SSE melalui ekstensi hx-ext="sse".
<!-- Load HTMX SSE extension -->
<script src="https://unpkg.com/htmx-ext-sse@2.2.2/sse.js"></script>
<!-- Membuka koneksi SSE dan mengikuti event -->
<div hx-ext="sse" sse-connect="/api/stream" sse-swap="message">
<!-- Konten dari event "message" akan di-swap ke sini -->
Menunggu data dari server...
</div>
<!-- Mengikuti beberapa event -->
<div hx-ext="sse" sse-connect="/api/events">
<div sse-swap="notification">
<!-- Event "notification" -->
</div>
<div sse-swap="status-update">
<!-- Event "status-update" -->
</div>
<div sse-swap="chat-message">
<!-- Event "chat-message" -->
</div>
</div>
<!-- Trigger HTMX request berdasarkan SSE event -->
<div hx-ext="sse" sse-connect="/api/events">
<button hx-get="/api/stats" hx-trigger="sse:refresh-stats" hx-target="#stats">
Stats (akan auto-refresh saat event "refresh-stats")
</button>
<div id="stats"></div>
</div>
const express = require('express');
const app = express();
app.get('/api/stream', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
let counter = 0;
const interval = setInterval(() => {
counter++;
// Format SSE: "event: <nama>\ndata: <isi>\n\n"
res.write(`event: message\ndata: <div class="update">Update #${counter} pada ${new Date().toLocaleTimeString()}</div>\n\n`);
}, 3000);
req.on('close', () => {
clearInterval(interval);
});
});
app.listen(3000, () => console.log('SSE server running on port 3000'));
7. WebSockets dengan HTMX
Untuk komunikasi dua arah penuh (bidirectional), HTMX mendukung WebSockets melalui ekstensi. Berbeda dari SSE yang hanya satu arah (server → klien), WebSockets memungkinkan klien mengirim pesan ke server secara real-time.
<!-- Load HTMX WebSocket extension -->
<script src="https://unpkg.com/htmx-ext-ws@2.0.2/ws.js"></script>
<!-- Koneksi WebSocket dengan send dan receive -->
<div hx-ext="ws" ws-connect="/ws/chat">
<!-- Messages dari server akan masuk ke #chatroom -->
<div id="chatroom" style="height:400px;overflow-y:auto;border:1px solid #333;padding:1rem;">
<p>Selamat datang di chat!</p>
</div>
<!-- Form ini mengirim data ke WebSocket saat submit -->
<form ws-send>
<input type="text" name="message" placeholder="Ketik pesan..." required>
<button type="submit">Kirim</button>
</form>
</div>
<!-- Mengirim data spesifik ke WebSocket -->
<button hx-ext="ws" ws-connect="/ws/notifications"
ws-send='{"action": "subscribe", "channel": "updates"}'>
Subscribe ke Updates
</button>
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
console.log('Client terhubung. Total:', clients.size);
ws.on('message', (data) => {
const msg = JSON.parse(data);
// Format: server mengembalikan HTML fragmen
const html = `<div class="chat-message">
<strong>User:</strong> ${msg.message}
<span class="time">${new Date().toLocaleTimeString()}</span>
</div>`;
// Broadcast ke semua client
for (const client of clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(html);
}
}
});
ws.on('close', () => {
clients.delete(ws);
});
});
console.log('WebSocket server berjalan di ws://localhost:8080');
8. Templates & Morphing
HTMX mendukung fitur template untuk menampilkan konten sementara (loading, error) dan morphing untuk transisi halaman yang mulus.
Content Morphing (Idiomorph)
<!-- Load Idiomorph extension untuk morphing -->
<script src="https://unpkg.com/htmx-ext morph@0.0.4/morph.js"></script>
<!-- Menggunakan morphing alih-alih swap biasa -->
<body hx-ext="morph">
<main hx-get="/new-content" hx-trigger="click" hx-target="#app"
hx-swap="morph">
<div id="app">
<h1>Halaman Awal</h1>
<p>Konten akan di-morph secara mulus</p>
</div>
</main>
</body>
View Transitions dengan HTMX
// Menggunakan View Transitions API bersama HTMX
document.addEventListener('htmx:beforeSwap', function(evt) {
if (document.startViewTransition) {
evt.preventDefault();
document.startViewTransition(() => {
evt.detail.shouldSwap = true;
htmx.swap(evt.detail.target, evt.detail.content, evt.detail);
});
}
});
// Mengatur transition name pada elemen
document.addEventListener('htmx:beforeSwap', function(evt) {
const target = evt.detail.target;
target.style.viewTransitionName = 'main-content';
});
Kombinasi HTMX dengan Alpine.js
<!-- HTMX untuk server interaction, Alpine.js untuk client state -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x"></script>
<div x-data="{ showModal: false, selectedUser: null }">
<!-- HTMX load user list dari server -->
<div hx-get="/api/users" hx-trigger="load" hx-target="#user-list">
<ul id="user-list">Loading users...</ul>
</div>
<!-- Modal dikelola oleh Alpine.js (client-side state) -->
<template x-if="showModal">
<div class="modal-overlay" @click.self="showModal = false">
<div class="modal">
<!-- Detail user di-load dari server via HTMX -->
<div hx-get="'/api/users/' + selectedUser"
hx-trigger="load"
hx-target="#user-detail">
<div id="user-detail">Loading...</div>
</div>
<button @click="showModal = false">Tutup</button>
</div>
</div>
</template>
</div>
9. Best Practices
- Gunakan hx-boost untuk progressive enhancement tanpa rewrite
- Server mengembalikan HTML — bukan JSON. HTMX memperkuat arsitektur server-centric
- Debounce input — selalu gunakan
delay:pada trigger search/input - Gunakan OOB Swaps (
hx-swap-oob="true") untuk update beberapa bagian halaman sekaligus - Manfaatkan CSS transitions dengan class
htmx-settlinguntuk animasi - Error handling — selalu tangani response error dengan
hx-target-4xxdanhtmx:responseError - CSRF Protection — gunakan
hx-headersatauhtmx:configRequestuntuk token - Audit request — pantau jumlah request yang dikirim (terutama polling) untuk efisiensi
- Kombinasikan dengan Alpine.js atau Hyperscript untuk client-side state
- Test tanpa JavaScript — pastikan website tetap berfungsi (progressive enhancement)
<!-- Request pertama: update target utama --> <form hx-post="/api/cart/add" hx-target="#cart-items"> <input type="hidden" name="product_id" value="42"> <button type="submit">Tambah ke Keranjang</button> </form> <!-- Server mengembalikan: --> <!-- 1. Fragmen untuk #cart-items (target utama) --> <div> <li>Produk A - Rp 50.000</li> <li>Produk B - Rp 75.000</li> </div> <!-- 2. Fragmen OOB untuk update badge di tempat lain --> <div id="cart-badge" hx-swap-oob="true"> 3 item </div> <!-- 3. Fragmen OOB untuk update total harga --> <div id="cart-total" hx-swap-oob="true"> Rp 175.000 </div>
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial HTMX, jawablah 5 pertanyaan berikut: