Skip to main content

django-htmx-nav

HTMX does the heavy lifting for server-driven, SPA-like UX with MPA simplicity, the URL is already the source of truth, and out-of-band swaps already let you update more than one region of the page in one response. What's missing isn't a framework; it's a clear road to implementing that pattern without the two things that bite in practice: stale navigation state (the sidebar/breadcrumbs say something different depending on whether you full-loaded or HTMX-swapped into a page) and cumbersome boilerplate (hand-wiring OOB fragments, push-url, redirects, and partial rendering for every view).

This package is small on purpose: two modules, no Django app to install.

  • htmx_nav.responsesrender_htmx, Oob, htmx_redirect, make_shell_renderer. The actual HTMX response mechanics: partial rendering, out-of-band fragments, push-url, cache-correct Vary headers, and HTMX-safe redirects.
  • htmx_nav.helpers (optional) — reverse_maybe, cache_on_request, and check_template_partials_configured. Small, generic functions useful when building your own nav data; nothing here is required to use responses.py.
  • htmx_nav.testingassert_shell_parity, which renders a URL both as a full page load and an HTMX request and asserts your nav state matches — the actual invariant ("stale nav state") this package exists to help you avoid, turned into a test you can run in CI.
  • htmx_nav.viewsmake_shell_view_mixin, a CBV convenience.

How you actually build nav data — a sidebar, breadcrumbs, tabs — is deliberately not something this package ships a class for. It's documented instead, as two worked patterns:

Install

htmx_nav is not a Django app, nothing to add to INSTALLED_APPS.

pip install "django-htmx-nav @ git+https://github.com/lucas-rollin/django-htmx-nav.git@v0.1.0"

Template partials setup

render_htmx addresses partial renders as template_name#partial_name.

  • Django ≥ 6: supported natively, nothing to configure. Define fragments with {% partialdef content %}...{% endpartialdef %}.
  • Django < 6: requires the django-template-partials package (already a dependency of django-htmx-nav) registered in your project's INSTALLED_APPS:
    INSTALLED_APPS = [
        ...,
        "template_partials",
    ]
    
    and {% load partials %} at the top of any template defining a {% partialdef %}.

Usage

Rendering a view

from htmx_nav.responses import render_htmx

def project_list(request):
    return render_htmx(request, "app/project_list.html", {"projects": Project.objects.all()})

On a full page load, this renders app/project_list.html in full. On an HTMX request, it renders only the {% partialdef content %} block, sets HX-Push-Url to the current URL (so the swap is bookmarkable/reload-safe), and sets Vary: HX-Request (so caches don't serve the wrong variant to the wrong request type).

Keeping a shell (sidebar/nav) in sync via OOB

from htmx_nav.responses import make_shell_renderer
from .nav import build_nav_context  # see docs/patterns/

render_shell = make_shell_renderer(
    shell_template="app/_shell.html",
    context_builder=lambda request: {"nav": build_nav_context(request)},
)

def project_detail(request, pk):
    project = get_object_or_404(Project, pk=pk)
    return render_shell(request, "app/project_detail.html", {"project": project})

Every HTMX response from render_shell includes the shell template as an out-of-band fragment, computed from the same build_nav_context whichever pattern you chose, so the sidebar/breadcrumbs can never drift from what a full page load would have shown.

Ad hoc OOB fragments

from htmx_nav.responses import Oob, render_htmx

def mark_read(request, pk):
    notification = get_object_or_404(Notification, pk=pk)
    notification.mark_read()
    return render_htmx(
        request, "app/notifications/_item.html", {"notification": notification},
        oobs=[Oob("app/notifications/_badge.html", {"count": unread_count(request.user)}, target_id="unread-badge")],
    )

Redirecting safely from either context

from htmx_nav.responses import htmx_redirect

def create_project(request):
    ...  # create it
    return htmx_redirect(request, reverse("app:project_detail", args=[project.pk]))

A plain HttpResponseRedirect gets swapped into the DOM as raw HTML when the triggering request was HTMX. Instead of navigating, htmx_redirect returns HX-Redirect + 204 on HTMX requests, a normal redirect otherwise.

Class-based views

from htmx_nav.views import make_shell_view_mixin
from .render import render_shell

ShellViewMixin = make_shell_view_mixin(render_shell)

class ProjectDetailView(ShellViewMixin, DetailView):
    template_name = "app/project_detail.html"

Enforcing nav parity in tests

from htmx_nav.testing import assert_shell_parity

def test_project_detail_nav_parity(client, project):
    assert_shell_parity(
        client, f"/projects/{project.pk}/",
        checks={
            "active_sidebar_item": lambda ctx: [i["label"] for i in ctx["nav"]["sidebar"] if i["active"]],
            "breadcrumbs": lambda ctx: [c["label"] for c in ctx["nav"]["breadcrumbs"]],
        },
    )

htmx_nav.helpers reference

  • reverse_maybe(view_name, kwargs=None, *, strict=True)reverse() with an escape hatch for reusing ambient kwargs (e.g. the current page's request.resolver_match.kwargs) across several nav links that don't all accept them. strict=True (default) behaves exactly like reverse(), pass strict=False when the kwargs are ambient/reused rather than built specifically for this view_name, and a mismatch should degrade gracefully (retry with no kwargs) instead of raising.
  • cache_on_request(request, key, builder), compute builder() at most once per request, e.g. so a main-content render and an OOB shell render in the same response cycle don't rebuild nav context twice.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_htmx_nav-0.1.0.tar.gz (12.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_htmx_nav-0.1.0-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file django_htmx_nav-0.1.0.tar.gz.

File metadata

  • Download URL: django_htmx_nav-0.1.0.tar.gz
  • Upload date:
  • Size: 12.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for django_htmx_nav-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e36d04c3e0572dcb55c403dcf4cd5157e329001715ba672b8d06bd8cda84c60a
MD5 d6e49e4250bdcb4ec5000c427fa91d38
BLAKE2b-256 5360b3ca302fc5baafe6b8572afd050994bf0ca395a130c81c945a0bb6717e50

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_htmx_nav-0.1.0.tar.gz:

Publisher: python-publish.yml on lucas-rollin/django-htmx-nav

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_htmx_nav-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: django_htmx_nav-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 11.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for django_htmx_nav-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5af2cf862745534dc9dc32da28dcce457892eb4fe6dc62ea3e1ecddb00c1bf41
MD5 0526caea334bb40375604d37593657b9
BLAKE2b-256 811cf444f7d06f2ff01db274eb5eaf77ee64cedfbc9b488a4030413a34cbf4d4

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_htmx_nav-0.1.0-py3-none-any.whl:

Publisher: python-publish.yml on lucas-rollin/django-htmx-nav

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page