Skip to main content

django-form-alpine

Build status on Github PyPI version Documentation PyPI - Python Version PyPI - Django Version Downloads

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. On DOMContentLoaded it reads window.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.) into window.DjangoFormAlpine unless 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:

  1. Container ID — ID of the closest tr.form-row or .inline-related, with hyphens replaced by underscores (e.g. items-0 → items_0). So __row_prefix__myField → items_0_myField.
  2. Element name — parsed with a prefix-number pattern (e.g. items-0-title → prefix items_0).
  3. 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)

Source distribution for django-form-alpine 0.0.6
File Size Uploaded
django_form_alpine-0.0.6.tar.gz 310.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-form-alpine 0.0.6
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.0.6 This release

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release 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