본문으로 건너뛰기 HTMX Complete Guide | Hypermedia, AJAX Without JavaScript

HTMX Complete Guide | Hypermedia, AJAX Without JavaScript

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:

AttributeDescription
hx-getSend GET request
hx-postSend POST request
hx-putSend PUT request
hx-deleteSend DELETE request
hx-triggerWhen to fire (default: natural event)
hx-targetWhere to put the response
hx-swapHow 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:

ValueEffect
innerHTMLReplace inner content (default)
outerHTMLReplace the element itself
beforebeginInsert before the element
afterbeginInsert at the start of the element
beforeendAppend at the end of the element
afterendInsert after the element
deleteRemove the element
noneDo 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:

HeaderEffect
HX-Redirect: /urlRedirect the browser
HX-Refresh: trueFull page refresh
HX-Trigger: event-nameTrigger a client-side event
HX-Retarget: #idOverride hx-target
HX-Reswap: strategyOverride 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-trigger supports delays, polling, and intersection observers out of the box
  • hx-swap-oob lets 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.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers HTMX, HTML, Web, Backend, Django, FastAPI, Hypermedia.