Alpine.js with Django
SkillDev toolsUse when adding, changing, or debugging Alpine.js behavior in Django-rendered templates, especially when Alpine.js coexists with HTMX partial updates.
Use Alpine.js with Django in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Alpine.js with Django and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Alpine.js with Django skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by hashgraph-online/awesome-codex-plugins in plugins/LVTD-LLC/skills/skills/alpinejs-django/SKILL.md and read by Ahel’s review.
Use this skill for local browser state in Django-rendered templates: dropdowns, modals, tabs, disclosures, inline filters, disabled/loading states, small preview interactions, and client-only toggles. Use Django views/forms/models as the source of truth for durable state. Use HTMX when the server needs to return fresh HTML.
Setup checks
- Identify how the project loads Alpine. Prefer the existing static asset or bundling pipeline as the source of truth.
- Find the base template, layout, or bundled entrypoint that loads HTMX, Alpine, and app JavaScript. Preserve the current ordering unless changing initialization deliberately.
- Determine whether the loaded Alpine file is self-starting or module-based. Do
not call
Alpine.start()when the project already loads a self-starting Alpine build. If using a module build, register components and start Alpine from the owning entrypoint. - Simple Alpine behavior should usually live inline in the Django template with
x-data. - If a reusable component or store is worth extracting, register it before
Alpine starts. Check the existing script order before relying on
document.addEventListener("alpine:init", ...)from an app-wide script. - When reusable Alpine registration is needed, add or reuse a script or
entrypoint that runs before Alpine starts. Register components and stores
inside
document.addEventListener("alpine:init", ...), include the file through the project's normal asset path, and avoid double-starting Alpine.
Ownership rules
- Django owns persistence, authorization, validation, redirects, and rendered HTML.
- HTMX owns server round trips and DOM swaps.
- Alpine owns ephemeral state already present in the browser.
- Plain JavaScript modules or bundled entrypoints own shared DOM behavior that is not naturally scoped to one Alpine component.
- Do not duplicate the same behavior in Alpine and a plain JS module.
Template patterns
Use a small x-data object near the markup it controls:
<div
x-data="{ open: false }"
@keydown.escape.window="open = false"
@click.outside="open = false"
>
<button
type="button"
:aria-expanded="open.toString()"
@click.stop="open = !open"
>
Menu
</button>
<div x-cloak x-show="open" x-transition>
...
</div>
</div>
Prefer Alpine directives over manual DOM manipulation:
x-showfor toggling visibility while keeping the element in the DOM.x-ifon a<template>when the element should be created and destroyed.x-modelfor client-only input state; Django form submission and validation still happen on the server.:class,:disabled,:aria-expanded, and:hiddenfor state-derived attributes.$watchfor one named state transition;x-effectonly when the dependency set is simple and intentional.$dispatchfor browser events between Alpine components or from Alpine to HTMX triggers.
Use x-cloak for anything hidden by default. Ensure the base CSS includes the
required [x-cloak] { display: none !important; } rule.
Django data
Keep Django interpolation out of complex JavaScript expressions when possible.
For simple values, prefer HTML attributes:
<div
data-initial-label="{{ object.name }}"
x-data="{ label: '' }"
x-init="label = $el.dataset.initialLabel"
>
<span x-text="label"></span>
</div>
For structured values, prefer Django's json_script and parse it in x-init:
{{ rows|json_script:"rows-data" }}
<div
x-data="{ rows: [] }"
x-init="rows = JSON.parse(document.getElementById('rows-data').textContent)"
>
<template x-for="row in rows" :key="row.id">
<span x-text="row.name"></span>
</template>
</div>
Avoid |safe inside Alpine expressions. If the browser must display text, use
x-text. Use x-html only for trusted, already-sanitized HTML; most HTML should
be rendered by Django or returned through HTMX.
Alpine and HTMX
When HTMX swaps a fragment containing x-data, Alpine should initialize the new
component automatically. Do not restart Alpine after HTMX swaps.
Keep persistent Alpine state outside HTMX targets that will be replaced:
<section x-data="{ panelOpen: false }">
<button type="button" @click="panelOpen = !panelOpen">Filters</button>
<div id="results" hx-get="{% url 'search_results' %}" hx-trigger="change from:#filters">
...
</div>
</section>
Use events as the boundary between HTMX responses and Alpine state. From Django,
set an HX-Trigger or HX-Trigger-After-Swap response header, then listen from
Alpine:
<div x-data="{ open: true }" @profile-saved.window="open = false">
<form hx-post="{% url 'profile_update' %}" hx-target="#profile-panel" hx-swap="outerHTML">
...
</form>
</div>
Listen for HTMX lifecycle events in kebab case from Alpine, such as
@htmx:after-swap.window, because HTML attributes are case-insensitive.
If Alpine dispatches an event that HTMX should react to, make the trigger explicit:
<button type="button" x-data @click="$dispatch('refresh-results')">
Refresh
</button>
<div hx-get="{% url 'results' %}" hx-trigger="refresh-results from:body">
...
</div>
Accessibility
- Keep real buttons as
<button type="button">unless submitting a form. - Bind ARIA state from Alpine state, especially
aria-expanded,aria-controls,aria-selected, andaria-hidden. - Support
@keydown.escape.windowfor dismissible overlays. - Use
@click.outsidefor popovers and menus, paired with a visible trigger. - Preserve focus behavior for modals, menus, and swapped HTMX content. If focus trapping is needed, add a focused plain JS module or Alpine plugin deliberately instead of hand-rolling a brittle trap in attributes.
Security and CSP
- Treat all Alpine expressions as JavaScript running in the user's browser.
- Do not put secrets, signed tokens, or privileged data into
x-data,data-*,json_script, or hidden inputs unless the user is allowed to see them. - If strict Content Security Policy without
unsafe-evalis required, switch deliberately to Alpine's CSP build and retest Alpine expressions. The defaultalpinejs/dist/cdn.min.jsbuild is not the CSP build. - If Alpine code makes
fetch()requests, include Django CSRF headers or use existing HTMX/Django forms instead.
Validation
- For template-only Alpine changes, run the Django template or view tests that
cover the page, plus
npm run lintif JavaScript modules changed. - For HTMX interactions, test the full page and the partial response path.
- If the project has a frontend build step, run it before relying on static output.
- Manually verify stateful controls in light and dark mode when changing visible UI behavior.
Signals
- GitHub stars
- 1k
- Forks
- 316
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
alpinejs-django- Source
- github.com/hashgraph-online/awesome-codex-plugins
github.com/hashgraph-online/awesome-codex-plugins
Related picks
Skill · thedaviddias
The pick for JavaScriptmodern-javascript-patterns
Skill · wshobson
The pick for JavaScriptpython-performance-optimization
Skill · wshobson
The pick for Pythonpython-pro
Skill · jeffallan
The pick for Pythondjango-targeted-mocking
Skill · hashgraph-online
The pick for Djangointegration-django
Skill · posthog
The pick for Django