Building Interactive UIs with Django and HTMX
Building Interactive UIs with Django and HTMX
HTMX lets you build modern, interactive web applications without writing JavaScript. Instead of building a separate frontend that communicates via JSON APIs, you return HTML fragments from your server and let HTMX swap them into the page. Let's see how to integrate it with Django.
Why HTMX?
Traditional SPAs require complex JavaScript frameworks, build pipelines, and state management libraries. HTMX takes a radically different approach: it extends HTML with attributes that enable dynamic interactions.
- No build step required - just include the script tag
- Server-side rendering means better SEO and faster initial loads
- Your Django templates become your 'components'
- Reduced complexity - no separate frontend codebase
- Progressive enhancement - works without JavaScript (mostly)
- Smaller bundle size - HTMX is ~14KB gzipped
Installation
First, add HTMX and django-htmx to your project:
# Install the Django integration
pip install django-htmx
# settings.py
INSTALLED_APPS = [
# ...
'django_htmx',
]
MIDDLEWARE = [
# ...
'django_htmx.middleware.HtmxMiddleware',
]
Then include HTMX in your base template:
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Example 1: Like Button
Let's build a like button that updates without a page refresh. This is a classic example that shows the power of HTMX.
<!-- templates/partials/like_button.html -->
<button hx-post="{% url 'like_post' post.id %}"
hx-swap="outerHTML"
class="flex items-center gap-2 px-4 py-2 rounded-lg
{% if user_liked %}bg-red-100 text-red-600{% else %}bg-gray-100{% endif %}">
<span>❤️</span>
<span>{{ post.like_count }}</span>
</button>
# views.py
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
@require_POST
def like_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
# Toggle like
like, created = PostLike.objects.get_or_create(
post=post, user=request.user
)
if not created:
like.delete()
user_liked = False
else:
user_liked = True
# Update count
post.like_count = post.likes.count()
post.save()
return TemplateResponse(
request,
'partials/like_button.html',
{'post': post, 'user_liked': user_liked}
)
How does this work?
2. HTMX intercepts the click and sends a POST request to
/posts/123/like/3. The view toggles the like and returns the updated button HTML
4. HTMX replaces the button (
hx-swap="outerHTML") with the response5. The UI updates instantly - no page reload needed
The key insight: the server returns HTML, not JSON. No client-side state to manage!
Example 2: Infinite Scroll
Loading more content as the user scrolls is another common pattern. HTMX makes this trivial:
<!-- templates/posts/list.html -->
<div id="post-list">
{% for post in posts %}
{% include 'partials/post_card.html' %}
{% endfor %}
{% if has_next %}
<div hx-get="{% url 'post_list' %}?page={{ next_page }}"
hx-trigger="revealed"
hx-swap="outerHTML"
hx-select="#post-list > *">
<span class="loading">Loading more...</span>
</div>
{% endif %}
</div>
The hx-trigger="revealed" attribute tells HTMX to fire the request when the element scrolls into view. The hx-select extracts just the post cards from the response, avoiding nested containers.
Example 3: Live Search
<!-- Search input with debounce -->
<input type="search"
name="q"
hx-get="{% url 'search' %}"
hx-trigger="keyup changed delay:300ms"
hx-target="#search-results"
placeholder="Search posts...">
<div id="search-results">
<!-- Results appear here -->
</div>
The delay:300ms modifier debounces the input, preventing a request on every keystroke. Results are loaded into #search-results as the user types.
Key Takeaways
- HTMX attributes (hx-get, hx-post, hx-swap, etc.) make HTML interactive
- Server returns HTML fragments, not JSON - use Django templates
- No JavaScript framework or build pipeline required
- Progressive enhancement friendly - degrades gracefully
- Perfect for Django's template-centric architecture
- Use django-htmx for request detection and response helpers
Related Posts
Introduction to Machine Learning with Python
Start your ML journey with Python and scikit-learn. Build your first machine learning models with practical examples.
1 min read
Welcome to Warbler
An introduction to Warbler, our modern blogging platform built for developers who love clean, fast, and beautiful experiences.
1 min read
Python Best Practices for 2024
Essential Python patterns and practices every developer should know. From type hints to dataclasses, level up your Python code.
1 min read
Redis Caching Patterns for Web Applications
Speed up your web application with Redis caching. Learn cache-aside, write-through, and other patterns with practical examples.
1 min read
Comments
Log in to leave a comment.
Log In
Loading comments...