Referensi cepat HTMX. Attribute hx-get, hx-post, hx-swap, hx-trigger, events, dan extensions. Perfect buat developer yang mau bikin web interaktif tanpa JavaScript berat.
Cara termudah pakai HTMX di proyek kamu.
<!-- Via CDN (paling cepat) -->
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<!-- Via npm -->
<script src="./node_modules/htmx.org/dist/htmx.min.js"></script>
<!-- Self-hosted (production recommended) -->
<script src="/assets/htmx.min.js"></script>Verifikasi HTMX loaded:
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof htmx !== 'undefined') {
console.log('HTMX version:', htmx.config.version);
}
});
</script>Atribut yang nentuin kapan HTMX ngirim request.
<!-- hx-get: GET request saat element diklik -->
<button hx-get="/api/users">Load Users</button>
<!-- hx-post: POST request -->
<button hx-post="/api/users" hx-vals='{"name": "John"}'>
Create User
</button>
<!-- hx-put: PUT request -->
<button hx-put="/api/users/1">Update</button>
<!-- hx-delete: DELETE request -->
<button hx-delete="/api/users/1" hx-confirm="Yakin mau hapus?">
Delete
</button>
<!-- hx-patch: PATCH request -->
<button hx-patch="/api/users/1">Partial Update</button>Nentuin event apa yang nge-trigger request.
<!-- Default: click untuk button/link, submit untuk form, change untuk input -->
<!-- Trigger dengan event custom -->
<button hx-get="/data" hx-trigger="click">Click Me</button>
<!-- Trigger saat input berubah -->
<input type="text" hx-get="/search" hx-trigger="keyup"
hx-target="#results" name="q">
<!-- Trigger dengan delay (debounce) -->
<input type="text" hx-get="/search" hx-trigger="keyup changed delay:500ms"
hx-target="#results" name="q">
<!-- Trigger saat element muncul di viewport -->
<div hx-get="/lazy-load" hx-trigger="revealed">
Loading...
</div>
<!-- Trigger saat page load -->
<div hx-get="/notifications" hx-trigger="load">
Loading notifications...
</div>
<!-- Multiple triggers -->
<button hx-get="/data" hx-trigger="click, keyup[ctrlKey && key=='r']">
Refresh
</button>
<!-- Trigger dari event lain -->
<div hx-get="/updated" hx-trigger="refreshData from:body">
Waiting for refresh...
</div>
<!-- Polling (request berkala) -->
<div hx-get="/live-data" hx-trigger="every 5s">
Auto-refresh setiap 5 detik
</div>Nentuin element mana yang akan diupdate dengan response.
<!-- this (default): element itu sendiri -->
<button hx-get="/data" hx-target="this">Load</button>
<!-- CSS selector -->
<button hx-get="/data" hx-target="#result">Load</button>
<!-- closest: cari ancestor terdekat -->
<button hx-get="/data" hx-target="closest .card">Update Card</button>
<!-- find: cari descendant pertama -->
<div hx-get="/data" hx-target="find .content">Load</div>
<!-- next / previous sibling -->
<button hx-get="/data" hx-target="next div">Load</button>
<!-- relative syntax (HTMX 2.0) -->
<button hx-get="/data" hx-target="closest div">Load</button>Nentuin gimana response dimasukkin ke target.
<!-- innerHTML (default): ganti isi element -->
<div hx-get="/content" hx-swap="innerHTML">Loading...</div>
<!-- outerHTML: ganti element beserta isinya -->
<div hx-get="/new-card" hx-swap="outerHTML">Old Card</div>
<!-- beforebegin: sebelum element -->
<div hx-get="/banner" hx-swap="beforebegin">Before me</div>
<!-- afterbegin: di awal isi element (prepend) -->
<ul hx-get="/new-item" hx-swap="afterbegin">
<li>Existing item</li>
</ul>
<!-- beforeend: di akhir isi element (append) -->
<ul hx-get="/new-item" hx-swap="beforeend">
<li>Existing item</li>
</ul>
<!-- afterend: setelah element -->
<div hx-get="/footer" hx-swap="afterend">After me</div>
<!-- delete: hapus element -->
<button hx-delete="/items/1" hx-target="closest .item" hx-swap="delete">
Delete
</button>
<!-- none: tidak swap apa-apa (berguna bareng hx-on) -->
<div hx-get="/track" hx-swap="none">Track click</div><!-- Swap dengan transisi (morph, settle) -->
<div hx-get="/content" hx-swap="innerHTML transition:true">
Content
</div>
<!-- Swap dengan focus preservation -->
<div hx-get="/form" hx-swap="innerHTML focus:true">
Form
</div>
<!-- Swap dengan scroll -->
<div hx-get="/more" hx-swap="beforeend scroll:bottom">
List
</div>
<!-- Swap timing -->
<div hx-get="/data" hx-swap="innerHTML settle:500ms">
Data
</div>Kirim data tambahan bersama request.
<!-- JSON values -->
<button hx-get="/search"
hx-vals='{"category": "tech", "sort": "desc"}'>
Search
</button>
<!-- JavaScript computed values -->
<button hx-get="/data"
hx-vals='js:{timestamp: Date.now()}'>
Load
</button>
<!-- Include form values -->
<form hx-post="/submit" hx-include="[name='email']">
<input name="name" value="John">
<input name="email" value="john@test.com">
<button>Submit</button>
</form><button hx-get="/api/data"
hx-headers='{"X-Requested-With": "HTMX"}'>
Load Data
</button>Update URL bar browser tanpa reload.
<!-- Push URL baru ke history -->
<a hx-get="/users/1" hx-target="#content" hx-push-url="true">
View User
</a>
<!-- Push URL dari response header -->
<a hx-get="/page" hx-push-url="/page">Go to Page</a>
<!-- Hapus entry (replace) -->
<a hx-get="/home" hx-push-url="false">Home</a>Upgrade semua link dan form jadi AJAX request.
<!-- Boost semua link di dalam element -->
<div hx-boost="true">
<a href="/page1">Page 1</a>
<a href="/page2">Page 2</a>
</div>
<!-- Boost form -->
<form hx-boost="true" action="/search" method="get">
<input name="q" type="text">
<button>Search</button>
</form><!-- hx-indicator: element yang di-show saat request loading -->
<button hx-get="/slow-endpoint" hx-indicator="#spinner">
Load Data
</button>
<span id="spinner" class="htmx-indicator">Loading...</span>
<!-- hx-disabled-elt: disable element saat request -->
<button hx-get="/data" hx-disabled-elt="this">
Load Data
</button>
<!-- Disable form saat submit -->
<form hx-post="/submit" hx-disabled-elt="button">
<button>Submit</button>
</form>CSS untuk indicator:
/* Default: hidden */
.htmx-indicator {
opacity: 0;
transition: opacity 200ms ease-in;
}
/* Show saat request */
.htmx-request .htmx-indicator,
.htmx-indicator.htmx-request {
opacity: 1;
}
/* Disable element saat request */
.htmx-request.htmx-disabled-elt,
.htmx-disabled-elt.htmx-request {
opacity: 0.5;
pointer-events: none;
}<!-- Event hooks -->
<div hx-get="/data"
hx-on::before-request="console.log('Request starting')"
hx-on::after-request="console.log('Request done')"
hx-on::before-send="console.log('About to send')"
hx-on::after-swap="console.log('Content swapped')"
hx-on::response-error="alert('Error!')"
hx-on::before-on-load="console.log('Processing response')">
Load Data
</div>// Listen untuk HTMX events
document.body.addEventListener('htmx:afterRequest', function(evt) {
console.log('Request to:', evt.detail.requestConfig.path);
console.log('Status:', evt.detail.xhr.status);
});
// Cancel request
document.body.addEventListener('htmx:beforeRequest', function(evt) {
if (someCondition) {
evt.preventDefault();
}
});
// Custom response handling
document.body.addEventListener('htmx:beforeOnLoad', function(evt) {
// Cek status code
if (evt.detail.xhr.status === 401) {
window.location.href = '/login';
evt.preventDefault();
}
});
// Trigger custom event
htmx.trigger('#myElement', 'refreshData');| Event | Kapan dipicu |
|---|---|
| htmx:beforeRequest | Sebelum request dikirim |
| htmx:beforeSend | Sebelum XHR.send() |
| htmx:afterRequest | Setelah request selesai (success atau error) |
| htmx:afterSwap | Setelah content swap |
| htmx:beforeSwap | Sebelum swap |
| htmx:responseError | Response error (4xx, 5xx) |
| htmx:sendError | Network error |
| htmx:timeout | Request timeout |
| htmx:load | HTMX selesai load element |
| htmx:configRequest | Sebelum request config difinalize |
HTMX ngerti beberapa response header untuk control behavior.
# Python (Flask) example
from flask import Response
@app.route('/api/data')
def data():
resp = Response(html_content)
# Trigger client-side event
resp.headers['HX-Trigger'] = 'showToast'
# Redirect client
resp.headers['HX-Redirect'] = '/dashboard'
# Refresh target
resp.headers['HX-Refresh'] = 'true'
# Retarget swap
resp.headers['HX-Retarget'] = '#other-element'
# Reswap
resp.headers['HX-Reswap'] = 'outerHTML'
return respimport json
# Multiple events dengan data
resp.headers['HX-Trigger'] = json.dumps({
"showToast": {"message": "Saved!"},
"updateCounter": {"count": 42}
})<!-- Include extension script -->
<script src="https://unpkg.com/htmx-ext-sse@2.0.0/sse.js"></script>
<!-- Register extension -->
<body hx-ext="sse">
<!-- Server-Sent Events -->
<div sse-connect="/events" sse-swap="message" hx-target="this" hx-swap="beforeend">
<!-- Content updates from SSE -->
</div>
</body>
<!-- JSON Encoding -->
<script src="https://unpkg.com/htmx-ext-json-enc@2.0.0/json-enc.js"></script>
<body hx-ext="json-enc">
<!-- hx-vals akan dikirim sebagai JSON body -->
<button hx-post="/api" hx-vals='{"key": "value"}'>Send JSON</button>
</body>
<!-- Debug extension -->
<script src="https://unpkg.com/htmx-ext-debug@2.0.0/debug.js"></script>
<body hx-ext="debug">
<!-- Semua HTMX events akan di-log -->
</body>
<!-- Remove me extension (remove element after swap) -->
<script src="https://unpkg.com/htmx-ext-remove-me@2.0.0/remove-me.js"></script>
<div hx-ext="remove-me">
<div remove-me="2s">This will disappear in 2 seconds</div>
</div><!-- Click to edit -->
<div id="title" hx-get="/edit/1" hx-target="this" hx-swap="outerHTML">
<h2>Click to edit title</h2>
</div>
<!-- Server returns the edit form -->
<!-- <form id="title" hx-put="/edit/1" hx-target="this" hx-swap="outerHTML"> --><div hx-get="/items?page=2"
hx-trigger="revealed"
hx-swap="afterend"
hx-target="this">
<div>Loading more...</div>
</div><div class="item" id="item-1"
hx-delete="/items/1"
hx-target="closest .item"
hx-swap="outerHTML"
hx-on::after-request="this.style.opacity = 0">
Item content
<button>Delete</button>
</div><input type="search"
name="q"
hx-get="/search"
hx-trigger="input changed delay:300ms, search"
hx-target="#search-results"
hx-indicator="#search-indicator"
placeholder="Search...">
<span id="search-indicator" class="htmx-indicator">
Searching...
</span>
<div id="search-results"></div><!-- hx-confirm dengan custom message -->
<button hx-delete="/items/1"
hx-confirm="Yakin mau hapus item ini? Tindakan ini tidak bisa dibatalkan."
hx-target="closest .item"
hx-swap="outerHTML">
Delete
</button>
<!-- Custom confirm via event -->
<script>
document.addEventListener('htmx:confirm', function(evt) {
evt.preventDefault();
if (evt.detail.question.includes('irreversible')) {
// Custom modal instead of native confirm()
showCustomModal(() => evt.detail.issueRequest(true));
} else {
evt.detail.issueRequest(true);
}
});
</script>// Trigger request manually
htmx.trigger('#myElement', 'submit');
// AJAX helper methods
htmx.ajax('GET', '/api/data', {
target: '#result',
swap: 'innerHTML'
});
// Find HTMX element
htmx.find('#myElement');
// Add/remove HTMX processing class
htmx.addClass('#myElement', 'htmx-request');
htmx.removeClass('#myElement', 'htmx-request');
// Get config value
httmx.config.includeIndicatorStyles;
// Parse HTML as HTMX
htmx.process(document.body);app.get('/users/:id', async (req, res) => {
const user = await getUser(req.params.id);
// Cek apakah request dari HTMX
if (req.headers['hx-request']) {
// Return HTML fragment
res.render('partials/user-card', { user });
} else {
// Return full page
res.render('pages/user-detail', { user });
}
});
// Partial render untuk HTMX
app.post('/tasks', async (req, res) => {
const task = await createTask(req.body);
res.setHeader('HX-Trigger', 'taskCreated');
res.setHeader('HX-Retarget', '#task-list');
res.render('partials/task-item', { task });
});Login atau daftar akun gratis untuk membaca cheat sheet ini.