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/[email protected]"></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.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Build modern web apps with HTMX — no JavaScript framework required. Covers hx-get, hx-post, hx-swap, infinite scroll, We… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [Django Complete Guide | MTV· ORM](/en/blog/django-complete-guide/
- [FastAPI Complete Guide | Python REST API· Async](/en/blog/fastapi-complete-guide/
이 글에서 다루는 키워드 (관련 검색어)
HTMX, HTML, Web, Backend, Django, FastAPI, Hypermedia 등으로 검색하시면 이 글이 도움이 됩니다.