Skip to main content

Integrate Alpine.js into Django forms declaratively using widget attrs and resolvers

Project description

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. 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)

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",
        })
    )

__row_prefix__ is resolved following Django's inline field naming convention (<prefix>-<number>-<field>):

  1. Container ID — ID of the closest tr.form-row or .inline-related, used as-is (e.g. items-0). So __row_prefix__myFielditems-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.

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>

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.

Project details


Download files

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

Source Distribution

django_form_alpine-0.0.3.tar.gz (33.1 kB view details)

Uploaded Source

Built Distribution

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

django_form_alpine-0.0.3-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

Details for the file django_form_alpine-0.0.3.tar.gz.

File metadata

  • Download URL: django_form_alpine-0.0.3.tar.gz
  • Upload date:
  • Size: 33.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_form_alpine-0.0.3.tar.gz
Algorithm Hash digest
SHA256 3d37348cee8d81068f688a504084cd60e00c9186fabf3bff04ecac12e9e6a930
MD5 c8e8ddd72ad1807159663967b8f84cb4
BLAKE2b-256 ed2805eb69c424ea2c2acb09d33713857c785bc4f868140d336227a1aa937861

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_form_alpine-0.0.3.tar.gz:

Publisher: release.yml on rodolvbg/django-form-alpine

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_form_alpine-0.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for django_form_alpine-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 85a18e9677416d6a2890c45bec05559b0893da5d217155c326f054883856b0aa
MD5 1468d104090d88ef1111e1f3bce75f69
BLAKE2b-256 21ffb5dd0421ee35166a2bbb645bb0f09d377c7c467db64482031e32a09578b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_form_alpine-0.0.3-py3-none-any.whl:

Publisher: release.yml on rodolvbg/django-form-alpine

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page