Product applications
Form
A finished page under Kiln. Copy it and it is yours outright: unlike a brickwork component or shell, it never upgrades under semver.
Static reference: This preview shows a fixed fictional invoice. Browsers may validate basic field rules, but server validation and invoice saving are not wired.
Scroll inside the frame to inspect the full page.
{% extends "brickwork/shell/app.html" %}
{% comment %}
A create/edit page: one form on a page.
COPY THIS FILE into your project and edit it. It is not on the template loader
path, so you cannot extend it (ADR-056).
Beat Phase D journey: Form validate (icvoss/django-brickwork#544). This file
IS the shared partial plus the 422 path: `{% partialdef form_region inline %}`
keeps the full-page render and the HTMX invalid re-render on one block.
Wire the view branches in docs/INTEGRATION.md section 4 (no-JS 200 full page,
HTMX 422 + `#form_region`, success HX-Redirect). Do not invent a second form
template.
What your view must supply:
form your own Django form (bound on POST)
nav_items / nav_active as in list.html
THE ONE RULE HERE: you own the <form> element, brickwork never emits it.
That is not a limitation, it is the only sound arrangement: only your project
knows its own action URL, and a template that wrapped your fields in its own
<form> would put your submit button outside it. So the <form>, the csrf_token,
and the submit all live together in this file, which you own.
States: valid/invalid per field (bw_form renders the bound form's own
errors inline); the 422 HTMX re-render path swaps this same region with
inline errors, never a full page reload, when wired.
Accessibility: inherits shell/app.html's skip link, sidebar/drawer nav and
page-header region; every field renders through the standard
forms/_field.html chrome (label/aria-describedby/error wiring); the
cancel link is a real anchor, never a button pretending to navigate.
Covered by the archetype harness's full gate sweep (render, axe WCAG 2.2
AA, no horizontal overflow, light/dark distinctness, skip-link
first-tab-stop with JS disabled) at every W0.1 breakpoint, both themes;
the underlying bw_form grid layout and its error states are additionally
covered by axe.spec.mjs against form-*.html/form-errors-*.html (axe on
the invalid state is the journey floor; keep that fixture green).
Responsive: no breakpoint switch of its own; inherits shell/app.html's
sidebar-to-drawer collapse at --bw-breakpoint-md (48rem). bw_form's own
grid layout (when grid_columns is set) collapses to one column below
--bw-breakpoint-sm (40rem), per that tag's own documentation.
{% endcomment %}
{% load brickwork_components brickwork_forms brickwork_nav %}
{% block page_title %}New invoice - Northwind{% endblock %}
{% block sidebar %}{% bw_nav nav_items nav_active %}{% endblock %}
{% block sidebar_mobile %}{% bw_nav nav_items nav_active %}{% endblock %}
{% block brand_wordmark %}Northwind{% endblock %}
{% block page_header %}
{% include "brickwork/components/_page_header.html" with title="New invoice" description="Raise an invoice against one of your accounts." %}
{% endblock %}
{% block content %}
<div class="bw-section-stack">
{% comment %}
Point the action at your own URL. {{ request.path }} posts back to this same
page, which is the usual shape for a create/edit view: on success the view
redirects, on failure it re-renders this page with the bound, invalid form
and the errors appear inline automatically.
bw_form renders form.visible_fields() generically, so it works with any
form: ModelForm, plain Form, allauth's, your own. It also includes the
non-field error region itself, so never include _form_errors.html a second
time beside it.
The hx-* attributes are progressive enhancement: without htmx the form is a
plain POST and the no-JS floor still works (BR-BW-HTMX-001). With htmx, an
invalid response must be 422 targeting this same region (see INTEGRATION.md).
{% endcomment %}
{% partialdef form_region inline %}
<form id="invoice-form" method="post" action="{{ request.path }}"
hx-post="{{ request.path }}"
hx-target="this"
hx-swap="outerHTML">
{% csrf_token %}
{% comment %}
Stacked fields with the amount control wrapped in input_group for a
currency prefix (icvoss/django-brickwork#624). Other fields use the
ordinary _field.html chrome; non-field errors stay above the stack.
{% endcomment %}
{% include "brickwork/forms/_form_errors.html" with form=form %}
<div class="bw-form-fields bw-form-fields--stacked">
{% for field in form.visible_fields %}
<div class="bw-field-row">
{% if field.name == "amount" %}
{% with errors=field.errors help_id=field.auto_id|add:"_help" error_id=field.auto_id|add:"_errors" %}
<div class="bw-field{% if errors %} bw-field--invalid{% endif %}">
<label class="bw-field__label" {% bw_attr "for" field.id_for_label %}>
{{ field.label }}{% if field.field.required %} <span class="bw-field__required" aria-hidden="true">*</span>{% endif %}
</label>
<div class="bw-field__control">
{% bw_field_widget field as amount_widget %}
{% include "brickwork/components/_input_group.html" with prefix="£" field=amount_widget %}
</div>
{% if field.help_text %}
<p class="bw-field__help" id="{{ help_id }}">{{ field.help_text }}</p>
{% endif %}
<div class="bw-field__errors" id="{{ error_id }}" role="alert">
{% for error in errors %}
<p class="bw-field__error">{{ error }}</p>
{% endfor %}
</div>
</div>
{% endwith %}
{% else %}
{% include "brickwork/forms/_field.html" with field=field %}
{% endif %}
</div>
{% endfor %}
</div>
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% comment %}
The submit sits INSIDE the form, beside the fields. A cancel link beside
it is an ordinary anchor, not a button: it navigates, it does not submit.
{% endcomment %}
<div class="bw-form__actions">
{% bw_button "Save invoice" type="submit" variant="primary" %}
{% bw_button "Cancel" variant="ghost" href="/invoices/" %}
</div>
</form>
{% endpartialdef %}
</div>
{% endblock %}
Details
| Kind | Page example |
|---|---|
| Used in | Product applications |
Composed from
| Template | Description |
|---|---|
brickwork/components/_button.html |
A button or link styled as a button, in several variants. |
brickwork/components/_input_group.html |
Prefix and/or suffix addons around a form-field control slot. |
brickwork/components/_page_header.html |
A page's title, description, and action row. |
brickwork/forms/_field.html |
Not a catalogue component |
brickwork/forms/_form_errors.html |
Not a catalogue component |
brickwork/nav/_nav.html |
Not a catalogue component |
brickwork/shell/app.html |
The authenticated app shell: sidebar, topbar, and content region. |