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

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-0items_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.

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>

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.5.tar.gz (33.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_form_alpine-0.0.5-py3-none-any.whl (30.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: django_form_alpine-0.0.5.tar.gz
  • Upload date:
  • Size: 33.9 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.5.tar.gz
Algorithm Hash digest
SHA256 98ad1aa7b7bc5a3d83fa42cf5e4411203adf07133b51ccfcef7d2fdcd5e5ba41
MD5 824dbc8fae8291a0c95e5ff9f5089c9a
BLAKE2b-256 e548b07e0ec3dbd9d95aa5028b590a78a13d177c57680deed31253b275ee592b

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_form_alpine-0.0.5.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.5-py3-none-any.whl.

File metadata

File hashes

Hashes for django_form_alpine-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 9d9b95a01662f7855674385b99e4f03b3f55d6d737bec03aba4c6ae8c9b46cdb
MD5 db2f63351093ed99ed7edd83460c2000
BLAKE2b-256 19a324d4b5855ec8037e8a2fc94a32dc85160c8724888ef8992dac2417f419dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_form_alpine-0.0.5-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