htmx-patterns

SKILL.md

HTMX Patterns for Django

Core Philosophy

  • Server renders HTML, not JSON - HTMX requests return HTML fragments, not data
  • Partial templates for dynamic updates - separate _partial.html files for HTMX responses
  • Progressive enhancement - pages work without JavaScript, HTMX enhances UX
  • Minimal client-side complexity - let the server do the heavy lifting

Critical Hints & Reminders

UX Best Practices

Always include loading indicators

  • Use hx-indicator to show loading states during requests
  • Users should never wonder if their action worked
  • Example: <button hx-get="/data/" hx-indicator="#spinner">Load</button>

Always provide user feedback

  • Use Django messages framework for success/error feedback
  • Return error messages in HTMX responses, not silent failures
  • Show what happened after an action completes

Handle errors gracefully

  • Return proper HTTP status codes (400 for validation errors, 500 for server errors)
  • Render form errors in partial templates
  • Don't swallow exceptions - log and show user-friendly messages

Django-Specific Patterns

Always detect HTMX requests

  • Check request.headers.get("HX-Request") to detect HTMX requests
  • Return partial templates for HTMX, full page templates otherwise
  • Pattern: if request.headers.get("HX-Request"): return render(request, "_partial.html", context)

Always return partials for HTMX

  • HTMX requests should return _partial.html templates, not full pages with base.html
  • Full page responses to HTMX requests break the UX and send duplicate HTML
  • Partials should be self-contained HTML fragments

Always validate request.method

  • Check request.method == "POST" before processing form data
  • Return proper status codes (405 Method Not Allowed for wrong methods)

CSRF is already configured globally

  • The base template has hx-headers on <body> - no need to add CSRF tokens to individual forms
  • All HTMX requests automatically include the CSRF token

Template Organization

Naming convention

  • Partials: _partial.html (underscore prefix)
  • Full pages: page.html (no prefix)
  • Example: posts/list.html (full page) includes posts/_list.html (partial)

Structure

  • Full page template extends base.html and includes partial
  • Partial contains only the dynamic HTML fragment
  • HTMX targets the partial's container div

Keep partials focused

  • Each partial should represent one logical UI component
  • Avoid partials that are too large or do too much
  • Compose larger UIs from multiple smaller partials

Django View Patterns

HTMX Detection

Check the HX-Request header to detect HTMX requests:

def my_view(request):
    context = {...}

    if request.headers.get("HX-Request"):
        return render(request, "app/_partial.html", context)

    return render(request, "app/full_page.html", context)

Form Handling Pattern

Key points:

  • Validate form normally
  • On success: return partial with new data OR trigger client-side event
  • On error: return partial with form errors
  • Always handle both HTMX and non-HTMX cases
def create_view(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            obj = form.save()
            if request.headers.get("HX-Request"):
                return render(request, "app/_item.html", {"item": obj})
            return redirect("app:list")

        # Return form with errors
        if request.headers.get("HX-Request"):
            return render(request, "app/_form.html", {"form": form})
    else:
        form = MyForm()

    return render(request, "app/create.html", {"form": form})

Response Headers Reference

HTMX respects special response headers for client-side behavior:

HX-Trigger

Trigger client-side events after response

  • Use case: Update other parts of page after action
  • Example: response["HX-Trigger"] = "itemCreated"
  • Template listens: <div hx-get="/count/" hx-trigger="itemCreated from:body">

HX-Redirect

Client-side redirect

  • Use case: Redirect after successful action
  • Example: response["HX-Redirect"] = reverse("app:detail", args=[obj.pk])

HX-Retarget / HX-Reswap

Override hx-target and hx-swap from server

  • Use case: Different targets for success vs error
  • Success: response["HX-Retarget"] = "#main"
  • Error: Return partial without changing target (targets the form)

HX-Refresh

Force full page refresh

  • Use case: Major state change that affects whole page
  • Example: response["HX-Refresh"] = "true"

Common Pitfalls

  • Missing loading indicators: Always use hx-indicator - users click multiple times without feedback
  • Full pages in HTMX responses: Return _partial.html, not full pages with base.html - check HX-Request header
  • Not handling form errors: Always return the form with errors on validation failure, not just the success case
  • Not disabling buttons: Use hx-disabled-elt="this" to prevent duplicate submissions
  • N+1 queries: HTMX views need select_related()/prefetch_related() just like regular views

Integration with Other Skills

  • django-templates: Partial template organization and inheritance patterns
  • django-forms: HTMX form submission and validation
  • django-extensions: Use show_urls to verify HTMX endpoints
  • pytest-django-patterns: Testing HTMX endpoints and headers
  • systematic-debugging: Debug HTMX request/response issues
Weekly Installs
11
GitHub Stars
84
First Seen
Jan 23, 2026
Installed on
opencode10
gemini-cli10
codex10
github-copilot9
cursor9
claude-code8