HTMX Complete Guide | Hypermedia, AJAX Without JavaScript
이 글의 핵심
HTMX brings AJAX, WebSockets, and server-sent events directly to HTML attributes — no build step, no framework. This guide covers the full HTMX toolkit with practical examples.
What This Guide Covers
HTMX lets you build dynamic web applications using HTML attributes instead of JavaScript. Your server returns HTML fragments; HTMX swaps them into the page. No build step, no npm, no framework.
Real-world insight: Replacing a React component with HTMX for a data table cut frontend complexity by 90% — the server already had the data, it just needed to return HTML instead of JSON.
Installation
No build step needed — just add the script tag:
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
Or install via npm for bundled projects:
npm install htmx.org
1. Core Attributes
HTMX adds behavior to HTML via hx-* attributes:
| Attribute | Description |
|---|---|
hx-get | Send GET request |
hx-post | Send POST request |
hx-put | Send PUT request |
hx-delete | Send DELETE request |
hx-trigger | When to fire (default: natural event) |
hx-target | Where to put the response |
hx-swap | How to insert the response |
2. Basic Example: Load Content
<!-- Click button → GET /api/users → swap into #user-list -->
<button hx-get="/api/users" hx-target="#user-list" hx-swap="innerHTML">
Load Users
</button>
<div id="user-list">Users will appear here</div>
Server returns an HTML fragment (not a full page):
# FastAPI example
@app.get("/api/users")
def get_users():
return HTMLResponse("""
<ul>
<li>Alice</li>
<li>Bob</li>
</ul>
""")
3. Swap Strategies
hx-swap controls how the response replaces content:
| Value | Effect |
|---|---|
innerHTML | Replace inner content (default) |
outerHTML | Replace the element itself |
beforebegin | Insert before the element |
afterbegin | Insert at the start of the element |
beforeend | Append at the end of the element |
afterend | Insert after the element |
delete | Remove the element |
none | Do nothing with the response |
<!-- Append a new item to a list -->
<form hx-post="/api/todos" hx-target="#todo-list" hx-swap="beforeend">
<input name="text" placeholder="New todo" />
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
4. Triggers
By default, HTMX fires on the natural event (click for buttons, change for inputs). Use hx-trigger to customize:
<!-- Fire on input with 300ms debounce -->
<input
hx-get="/api/search"
hx-trigger="input delay:300ms"
hx-target="#results"
name="q"
placeholder="Search..."
/>
<!-- Fire every 5 seconds (polling) -->
<div hx-get="/api/stats" hx-trigger="every 5s" hx-target="this">
Loading stats...
</div>
<!-- Fire when element enters the viewport -->
<div hx-get="/api/more" hx-trigger="intersect once" hx-swap="afterend">
Loading more...
</div>
5. Forms
HTMX serializes forms automatically:
<form hx-post="/api/login" hx-target="#result">
<input name="email" type="email" />
<input name="password" type="password" />
<button type="submit">Login</button>
</form>
<div id="result"></div>
Server validates and returns either a success fragment or an error fragment:
@app.post("/api/login")
def login(email: str = Form(), password: str = Form()):
if valid(email, password):
return HTMLResponse('<div class="success">Welcome!</div>')
return HTMLResponse('<div class="error">Invalid credentials</div>', status_code=401)
6. Infinite Scroll
<div id="posts">
{% for post in posts %}
<article>{{ post.title }}</article>
{% endfor %}
<!-- Trigger when this element enters the viewport -->
<div
hx-get="/api/posts?page={{ next_page }}"
hx-trigger="intersect once"
hx-target="#posts"
hx-swap="beforeend"
>
<p>Loading more...</p>
</div>
</div>
The server response includes the next batch of items AND a new trigger element with page={{ next_page + 1 }}.
7. Loading States with hx-indicator
<style>
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: block; }
</style>
<button hx-get="/api/slow-data" hx-target="#output" hx-indicator="#spinner">
Load Data
</button>
<span id="spinner" class="htmx-indicator">Loading...</span>
<div id="output"></div>
8. Out-of-Band Swaps
Update multiple parts of the page from a single response using hx-swap-oob:
<!-- Server response can include OOB swaps for other elements -->
@app.post("/api/add-item")
def add_item():
return HTMLResponse("""
<!-- Main response: the new item -->
<li>New Item</li>
<!-- OOB: also update the counter -->
<span id="item-count" hx-swap-oob="true">5 items</span>
""")
9. WebSockets
<div hx-ws="connect:/ws/chat">
<div id="messages"></div>
<form hx-ws="send">
<input name="message" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
</div>
Server sends HTML fragments over the WebSocket connection; HTMX swaps them in automatically.
10. Django Integration
# views.py
from django.http import HttpResponse
def search(request):
q = request.GET.get("q", "")
results = Product.objects.filter(name__icontains=q)
html = "".join(f"<li>{p.name}</li>" for p in results)
return HttpResponse(f"<ul>{html}</ul>")
<!-- template -->
<input
hx-get="{% url 'search' %}"
hx-trigger="input delay:300ms"
hx-target="#results"
name="q"
/>
<div id="results"></div>
HTMX Response Headers
Control HTMX behavior from the server via response headers:
| Header | Effect |
|---|---|
HX-Redirect: /url | Redirect the browser |
HX-Refresh: true | Full page refresh |
HX-Trigger: event-name | Trigger a client-side event |
HX-Retarget: #id | Override hx-target |
HX-Reswap: strategy | Override hx-swap |
response = HTMLResponse("<div>Done</div>")
response.headers["HX-Trigger"] = "itemAdded"
response.headers["HX-Redirect"] = "/dashboard"
Key Takeaways
- HTMX returns HTML, not JSON — the server does the rendering
hx-triggersupports delays, polling, and intersection observers out of the boxhx-swap-ooblets one response update multiple page regions- HTMX pairs naturally with Django, FastAPI, Rails, and any server-side framework
- No build step — drop in the script tag and start writing
hx-*attributes
HTMX is the right tool when your app is content-heavy, your team prefers backend languages, and you want to avoid the complexity of a full JavaScript framework.
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Build modern web apps with HTMX — no JavaScript framework required.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
Keywords Covered in This Article (Related Search Terms)
This article covers HTMX, HTML, Web, Backend, Django, FastAPI, Hypermedia.