django-crispy-forms
Control Django form HTML from Python layouts instead of hand-written templates, with pluggable Bootstrap, Tailwind, and Bulma output.
Repository Health
Technical Analysis
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|crispyfilter for rendering a form with one line instead of manual field-by-field markup - A
Layout/FormHelperAPI for composing form structure in Python using objects likeDiv,Row,Fieldset,Field, andMultiField - Swappable template packs (Bootstrap 3/4/5 via companion packages, Tailwind, Bulma, Foundation) controlled by a single
CRISPY_TEMPLATE_PACKsetting - 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.
Used by 2 apps in this directory
Sentry
Security · Developer Tools · Monitoring
Developer-first error tracking and performance monitoring platform with AI-powered root-cause analysis across 20+ languages and frameworks.
Weblate
Developer Tools
Continuous localization platform that commits translations directly into your version control system with full translator attribution.