django-form-alpine
django-form-alpine integrates Alpine.js into Django forms declaratively — you write Alpine directives directly in your widget attrs, and the library resolves which surrounding DOM element each directive should land on.
A Django Admin preset is included out of the box, so you get zero-config support for all standard admin containers. You can also define your own resolvers to use the library in any Django form, outside of the admin.
Installation
pip install django-form-alpine
Add django_form_alpine to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"django_form_alpine",
# ...
]
How it works
The library has two layers:
core.js— the engine. OnDOMContentLoadedit readswindow.DjangoFormAlpine.resolvers, iterates over all form inputs, and for each resolver it applies any matching prefixed attributes to the resolved container.admin.js— the Django Admin preset. Registers built-in resolvers for all standard admin containers (.form-row,fieldset,.field-box,.inline-related, etc.) intowindow.DjangoFormAlpineunless you supply your own.
Two mixins are provided:
| Mixin | Loads | Use when |
|---|---|---|
FormAlpineMixin |
core.js + alpine.js |
Any Django form with custom resolvers |
AdminAlpineMixin |
admin.js + core.js + alpine.js |
Django Admin (built-in resolvers included) |
contrib.unfold.UnfoldAdminAlpineMixin |
contrib/unfold.js + core.js (Unfold's Alpine.js) |
django-unfold's admin |
Quick start
Django Admin
Inherit from AdminAlpineMixin in your ModelAdmin or admin Form:
from django.contrib import admin
from django_form_alpine import AdminAlpineMixin
from .models import MyModel
@admin.register(MyModel)
class MyModelAdmin(AdminAlpineMixin, admin.ModelAdmin):
pass
That's it — the admin preset resolvers are loaded automatically.
Any Django form
Use FormAlpineMixin and define your own resolvers (see Custom resolvers):
from django import forms
from django_form_alpine import FormAlpineMixin
class MyForm(FormAlpineMixin, forms.ModelForm):
class Meta:
model = MyModel
fields = "__all__"
Features
Synchronize state with x-add-model-data
Add x-add-model-data to a widget to automatically register the field in the form's x-data and bind it with x-model:
class MyForm(forms.ModelForm):
my_field = forms.CharField(
widget=forms.TextInput(attrs={
"x-add-model-data": "myFieldState"
})
)
This initializes myFieldState in the closest form[x-data] and sets x-model="myFieldState" on the input.
Prefixed directives
Apply Alpine.js directives to surrounding containers directly from widget attrs using the x-<resolver>-<directive> (or @<resolver>-<directive>) pattern.
Built-in resolvers
Always available regardless of which mixin or resolvers you use:
| Prefix | Target |
|---|---|
self |
The input element itself |
Useful in formsets or any case where you want to apply a directive directly to the input rather than a surrounding container:
my_field = forms.CharField(
widget=forms.TextInput(attrs={
"x-self-show": "isVisible",
})
)
Django Admin preset resolvers
| Prefix | Target container |
|---|---|
form-row |
Closest .form-row |
form-multiline |
Closest .form-multiline |
form |
Closest form |
fieldset |
Closest fieldset |
field-box |
Closest .field-box (falls back to label.parentElement, then el.parentElement) |
field-container |
Parent of the field box |
label |
Field's label (inside .flex-container or .form-row) |
errorlist |
.errorlist inside the field container |
help |
.help inside the field container |
inline-container |
tr.form-row or .inline-related |
nonfield-errorlist |
.errorlist.nonfield in tabular or stacked inlines |
option-label |
Closest label (for checkboxes and radios) |
td |
Closest td (for tabular formset cells) |
Example: show/hide a form row
class MyForm(forms.ModelForm):
toggle = forms.BooleanField(
widget=forms.CheckboxInput(attrs={
"x-add-model-data": "showExtra"
})
)
extra_field = forms.CharField(
widget=forms.TextInput(attrs={
"x-form-row-show": "showExtra"
})
)
Inline forms with __row_prefix__
Use __row_prefix__ to namespace Alpine state keys per inline row, so each row has its own independent state:
class MyInlineForm(AdminAlpineMixin, forms.ModelForm): # or FormAlpineMixin with custom resolvers
my_field = forms.CharField(
widget=forms.TextInput(attrs={
"x-add-model-data": "__row_prefix__myField",
"x-field-box-show": "__row_prefix__otherField",
})
)
Hyphens in the row ID are converted to underscores so the result is a valid JavaScript identifier for Alpine.js:
- Container ID — ID of the closest
tr.form-rowor.inline-related, with hyphens replaced by underscores (e.g.items-0→items_0). So__row_prefix__myField→items_0_myField. - Element
name— parsed with aprefix-numberpattern (e.g.items-0-title→ prefixitems_0). - Empty string —
__row_prefix__is simply removed if neither applies.
Elements with __prefix__ in their name or ID (Django's empty form template rows) are automatically skipped.
Dynamically added rows (via the "Add another" button) are handled automatically — core.js listens for the formset:added event and processes each new row the same way as on initial load.
Custom resolvers
Use FormAlpineMixin and define your resolvers in a <script> tag before the form's scripts load:
<script>
window.DjangoFormAlpine = {
resolvers: {
"my-section": (el) => el.closest(".my-section"),
"my-label": (el) => el.closest(".my-label"),
},
};
</script>
Each resolver is a function (el) => HTMLElement | null where el is the form input element.
When you provide your own resolvers, the admin preset is not loaded. If you want both, set useAdminResolvers: true:
<script>
window.DjangoFormAlpine = {
useAdminResolvers: true,
resolvers: {
// Your custom resolver — merged on top of the admin ones
"my-section": (el) => el.closest(".my-section"),
},
};
</script>
Themes
- django-unfold:
UnfoldAdminAlpineMixin, with Unfold's own Alpine.js.
Configuration
To use a custom Alpine.js bundle instead of the one included with the package:
# settings.py
django_form_alpine_JS_PATH = "path/to/your/custom-alpine.js"
Changelog
See CHANGELOG.md for full release notes.
Contributing
See CONTRIBUTING.md
License
This project is licensed under the MIT License - see the LICENSE file for details.
Metadata
Release files for django-form-alpine 0.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_form_alpine-0.0.6.tar.gz | 310.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_form_alpine-0.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 343.4 kB
Release files / django_form_alpine-0.0.6.tar.gz
| Download URL | django_form_alpine-0.0.6.tar.gz |
|---|---|
| Size | 310.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
24d61744b750841482c667e841f5016d8b895b7c13a7bcfd8019feb7d78883fb
|
|
BLAKE2b-256 checksum How to use checksums |
0236c1030a0c7640fc1bbc2aba4e6d23e5f36f2f66ac1f466b1954e27f21b8bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / django_form_alpine-0.0.6-py3-none-any.whl
| Download URL | django_form_alpine-0.0.6-py3-none-any.whl |
|---|---|
| Size | 33.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
86a2e4496640b6618f18a0bb3fead07b8b70870cbc70a033f09858ef48c0b8ac
|
|
BLAKE2b-256 checksum How to use checksums |
907afaff4f2f15fe72b759f96b2a915627fee6d3215279184a594bd9992b408d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log