The modern frontend stack has become absurdly complex. A simple CRUD app now requires React, a bundler, a state management library, client-side routing, an API layer, and a build pipeline. htmx challenges this entire premise by extending HTML itself. Any element can issue HTTP requests. Any response can replace any part of the page. You write server-rendered HTML and sprinkle htmx attributes where you need dynamic behavior. No JavaScript files. No build step. No node_modules. The 14KB library handles the rest.
The Core Idea
In traditional web development, only <a> tags and <form> elements can make HTTP requests. htmx removes that limitation. Any element can trigger any HTTP method, target any element for replacement, and use any event as a trigger. The server returns HTML fragments, not JSON. The browser swaps them into the page. This is the hypermedia approach: the server controls application state and the UI, the client handles presentation.
Basic Patterns
<!-- Load htmx from CDN -->
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<!-- Click to load content into a div -->
<button hx-get="/api/users" hx-target="#user-list" hx-swap="innerHTML">
Load Users
</button>
<div id="user-list"></div>
<!-- Inline editing: click text to turn it into an edit form -->
<div hx-get="/contacts/1/edit" hx-trigger="click" hx-swap="outerHTML">
Click to edit: John Doe
</div>
<!-- Delete with confirmation -->
<button hx-delete="/contacts/1"
hx-confirm="Are you sure?"
hx-target="closest tr"
hx-swap="outerHTML swap:500ms">
Delete
</button>
<!-- Live search with debounce -->
<input type="search" name="q"
hx-get="/search"
hx-trigger="input changed delay:300ms"
hx-target="#results"
placeholder="Search contacts...">
<div id="results"></div>
Each attribute is self-explanatory: hx-get makes a GET request, hx-target selects where to put the response, hx-swap controls how (innerHTML, outerHTML, beforeend, etc.), and hx-trigger defines the event. The server returns an HTML fragment, not a full page.
Server-Side: Return HTML Fragments
# Python / Flask example
from flask import Flask, render_template_string, request
app = Flask(__name__)
contacts = [
{"id": 1, "name": "Alice Chen", "email": "alice@example.com"},
{"id": 2, "name": "Bob Martinez", "email": "bob@example.com"},
]
@app.route("/search")
def search():
q = request.args.get("q", "").lower()
results = [c for c in contacts if q in c["name"].lower()] if q else contacts
# Return an HTML fragment, not JSON
return render_template_string("""
{% for contact in results %}
<tr>
<td>{{ contact.name }}</td>
<td>{{ contact.email }}</td>
<td>
<button hx-delete="/contacts/{{ contact.id }}"
hx-target="closest tr" hx-swap="outerHTML">Delete</button>
</td>
</tr>
{% endfor %}
""", results=results)
@app.route("/contacts/<int:contact_id>", methods=["DELETE"])
def delete_contact(contact_id):
contacts[:] = [c for c in contacts if c["id"] != contact_id]
return "" # Empty response removes the row
Advanced Patterns
Infinite scroll. Add hx-get="/items?page=2" hx-trigger="revealed" hx-swap="afterend" to the last element. When it scrolls into view, htmx fetches the next page and appends it.
Optimistic UI with CSS transitions. The hx-swap="outerHTML swap:500ms" attribute delays removal, giving your CSS transition time to animate. Pair with htmx-swapping and htmx-settling CSS classes for enter/exit animations.
WebSocket integration. The hx-ws extension connects to a WebSocket and swaps incoming messages into the DOM. Real-time updates with zero client-side state management.
Out-of-band swaps. The server can update multiple elements in a single response using hx-swap-oob="true". Update the notification badge, the sidebar count, and the main content area all from one request.
When htmx Is the Right Choice
htmx excels at server-rendered applications with moderate interactivity: admin panels, dashboards, CRUD apps, content management systems, internal tools, and any application where the server already owns the data and business logic. It pairs naturally with Django, Rails, Laravel, Flask, Express, Go, and any server framework that renders HTML templates.
htmx is not the right choice for highly interactive client-side applications (collaborative editors, complex drag-and-drop, real-time games) or offline-first PWAs. If your app requires complex client-side state management or heavy computation in the browser, a JavaScript framework is the better tool.
The value proposition is simplicity. A team of backend developers can build a fully dynamic web application without learning React, without maintaining a separate API layer, and without a JavaScript build pipeline. That is a genuine productivity multiplier for the right class of applications.
Further reading: htmx Documentation | htmx Examples | Hypermedia Systems (free book)

Leave a Reply