django-crispy-forms

Control Django form HTML from Python layouts instead of hand-written templates, with pluggable Bootstrap, Tailwind, and Bulma output.

Library
PyPI
v2.7
5,156stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
67/100Good
Development Activity44
Maintenance36
Community88
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture74
Code Quality78
Innovation58
Learning Curve80

django-crispy-forms gives Django developers a way to render forms without writing repetitive template HTML. Instead of manually laying out every <div> and <label> in a template, you define a Layout object in Python using composable elements like Div, Row, Field, and Fieldset, then attach it to a form via a FormHelper. The {% crispy %} tag or |crispy filter renders the whole form from that configuration.

Output markup is delegated to swappable “template packs” selected through the CRISPY_TEMPLATE_PACK setting, so the same layout definition can render as Bootstrap 3/4, Tailwind, Bulma, or a custom company style by installing a companion package (crispy-bootstrap4, crispy-tailwind, crispy-bulma, etc.) and swapping the setting — without touching form or view code.

What You Get

  • A {% crispy %} template tag and |crispy filter for rendering a form with one line instead of manual field-by-field markup
  • A Layout/FormHelper API for composing form structure in Python using objects like Div, Row, Fieldset, Field, and MultiField
  • Swappable template packs (Bootstrap 3/4/5 via companion packages, Tailwind, Bulma, Foundation) controlled by a single CRISPY_TEMPLATE_PACK setting
  • Layout introspection helpers (helper.filter(), helper.all(), filter_by_widget()) for dynamically modifying a layout at runtime
  • Bootstrap-specific layout objects (PrependedText, AppendedText, FieldWithButtons, StrictButton) for common input-group patterns

Common Use Cases

  • Rendering a consistent set of form templates across a whole Django project without repeating markup in every template file
  • Migrating a Django app’s forms to a new CSS framework by swapping the template pack instead of rewriting templates
  • Building complex multi-column or grouped form layouts (wizards, nested fieldsets) driven from Python rather than nested template includes
  • Dynamically altering which fields are visible or how they’re grouped based on form state, using the layout-filtering API

Under The Hood

Architecture The library splits cleanly into a configuration layer and a rendering layer. FormHelper (crispy_forms/helper.py) holds per-form behavior (method, action, buttons, CSS) and a reference to a Layout tree (crispy_forms/layout.py) built from composable LayoutObject nodes (Div, Row, Fieldset, Field, MultiField); each node implements render() and a get_template_name() via TemplateNameMixin so the same tree can target different template packs. LayoutSlice (layout_slice.py) provides a query/mutation API (helper.filter(), .all(), .wrap()) over that tree, letting callers reach into an existing layout and change it at runtime rather than rebuilding it. The {% crispy %} tag and |crispy filter (templatetags/crispy_forms_tags.py, crispy_forms_filters.py) are the entry points: they resolve the active template pack from the CRISPY_TEMPLATE_PACK setting, then delegate to utils.render_field/render_crispy_form to walk the layout and render each node’s template. Swapping frameworks (Bootstrap, Tailwind, Bulma) means swapping which template pack the same layout tree resolves against, not touching layout code.

Tech Stack Pure Python targeting Django 5.2+ on Python 3.10+, with no runtime dependencies beyond Django itself. Layout nodes use dataclasses for lightweight value objects (e.g. Pointer in layout.py) and Django’s own template engine (django.template.loader.render_to_string, Template) for actual HTML output — there is no separate templating system. Companion packages (crispy-bootstrap3, crispy-bootstrap4, crispy-tailwind, crispy-bulma) ship the actual .html templates per framework and are pulled in as dev/test dependencies via requirements/*.txt. Tooling is black/isort/flake8 for style, tox to run the test matrix across Python versions, and GitHub Actions CI (.github/workflows/main.yml) running that matrix plus a dedicated lint job and Codecov upload.

Code Quality Tests live under tests/ (test_layout.py, test_layout_objects.py, test_form_helper.py, test_tags.py, test_dynamic_api.py, test_utils.py) using pytest with pytest-django, plus golden-output fixtures in tests/results/ that rendered HTML is diffed against — a stronger check than typical assertion-only test suites. Coverage is tracked via coverage[toml]/Codecov and enforced in CI across five Python versions (3.10-3.14). Code style is auto-formatted with black and import-sorted with isort; flake8 runs as its own lint job. Type hints are used selectively (e.g. Pointer dataclass, list[int] annotations) rather than throughout, and the codebase relies on __getattr__ delegation patterns (LayoutObject) that trade some static-analysis clarity for API ergonomics.

API Design The public surface is deliberately small: import FormHelper and a handful of layout classes, attach a Layout(...) to helper.layout, and add one {% crispy form form.helper %} tag to a template. Layout objects compose the way native Python containers do (__getitem__, __setitem__, __len__, list-method delegation via __getattr__), so building or editing a layout tree feels like manipulating lists rather than learning a bespoke DSL. Extensive docstrings on layout classes (e.g. bootstrap.py’s PrependedAppendedText) document parameters and framework-specific quirks inline, and the readthedocs site covers each layout object and template pack, keeping the learning curve low despite the underlying tree structure.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search