Product applications
List
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 uses fixed fictional invoice data. Its filters, sorting, pagination and New invoice action are rendered sample controls, not working invoice operations here.
Scroll inside the frame to inspect the full page.
{% extends "brickwork/shell/app.html" %}
{% comment %}
An index page: filter bar, table of records, pagination.
COPY THIS FILE into your project and edit it. It is not on the template loader
path, so you cannot extend it (ADR-056); copying is the only way to use it.
Beat Phase D journey: List + filter (icvoss/django-brickwork#544). Composes
`_filter_bar.html` + `_data_table.html` (empty branch is `_empty_state.html`)
with a Clear path (`clear_href` on the bar; `empty_action_*` on the table when
filters yield zero rows) and phone stack (`responsive="stack"`).
What your view must supply, and nothing else:
invoices a page of records (a Django Page object from Paginator)
filter_form your own filter form (any form; the bar renders its fields)
The rest of this page is content typed directly into the template, which is
where you change it.
This example extends the shipped app shell, which stays a supported importable
part of the package. Swap the extends line for your own base.html (see
examples/base.html) if you would rather own the document skeleton too.
States: sortable-column ascending/descending/unsorted per column (see
_data_table.html's own header); pagination rendered/absent (a single
page renders no controls); the filter bar's own field states; empty
table with a Clear filters action when the view passes zero rows after
a filter.
Accessibility: inherits shell/app.html's skip link, sidebar/drawer nav and
page-header region; the filter bar, table and pagination all render as
real form/table/nav landmarks with no client-side-only behaviour
required. This is the archetype the shell's own account-menu-open and
topbar-layout fixtures are also built from (list-menu-open-*.html,
list-topbar-*.html), so its composed shell chrome is exercised in both
the sidebar and topbar layout shapes, not only the default one. 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, and by
axe.spec.mjs's own "no-JS floor" suite, which asserts this page's real
server-rendered table rows, breadcrumbs and account menu all work with
JavaScript disabled.
Responsive: inherits shell/app.html's sidebar-to-drawer collapse at
--bw-breakpoint-md (48rem). The table uses responsive="stack" so each
row becomes a labelled card stack on narrow viewports (phone), not a
sideways scroll. The filter bar's fields wrap fluidly with no named
breakpoint.
{% endcomment %}
{% load brickwork_components brickwork_nav %}
{% block page_title %}Invoices - Northwind{% endblock %}
{% comment %}
The sidebar. bw_nav renders your NavItem tree and resolves the active item from
the current request. Build the tree once in your own nav config module and put
the visibility-filtered items plus the active item into context from a context
processor, so every page gets them without repeating the wiring:
nav_items visible_items(MY_NAV, request)
nav_active resolve_active_item(MY_NAV, request)
Rendered twice on purpose: once for the desktop sidebar and once inside the
shell's mobile drawer. One nav definition, two containers.
{% endcomment %}
{% 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="Invoices" description="Every invoice raised against your accounts, newest first." %}
{% endblock %}
{% comment %}
Actions sit in the shell's own page_actions block, beside the header rather
than inside it. Point href at your own {% templatetag openblock %} url
{% templatetag closeblock %}.
{% endcomment %}
{% block page_actions %}
{% bw_button "New invoice" variant="primary" icon="plus" href="/invoices/new/" %}
{% endblock %}
{% block content %}
<div class="bw-section-stack">
{% comment %}
The filter bar is a plain <form method="get">, so it filters with zero JS.
Passing hx_get and hx_target progressively enhances it to swap just the table
instead of navigating. Both work; the htmx path is an addition, never a
requirement.
{% endcomment %}
{% include "brickwork/components/_filter_bar.html" with fields=filter_form submit_label="Filter" clear_href="/invoices/" hx_get="/invoices/" hx_target="#invoices-table" %}
{% comment %}
The table renders structure only: your view decides the ordering and hands
back the ordered rows. Sortable columns need sort_key (the ascending key);
the component derives the descending key and the next-click toggle itself.
Each row's cells are pre-rendered strings you build in the view, so a cell
can carry whatever markup that record needs.
{% endcomment %}
{% comment %}
responsive="stack" is the phone contract for this journey: each row stacks
as labelled cards below the table's stack breakpoint instead of forcing
horizontal scroll. empty_action_* is the filtered-empty clear path: when
the view returns zero rows after a filter, the table's empty state offers
the same clear URL as the filter bar. For a true never-had-data empty,
pass empty_action_href/label pointing at create instead (or omit both).
{% endcomment %}
{% include "brickwork/components/_data_table.html" with table_id="invoices-table" columns=invoice_columns rows=invoice_rows current_sort=current_sort responsive="stack" empty_heading="No invoices match these filters" empty_body="Clear the filters to see every invoice, or raise a new one." empty_action_href="/invoices/" empty_action_label="Clear filters" %}
{% comment %}
Renders nothing at all when there is only one page, so there is no condition
to write here. It keeps the current sort and filters across a page change.
page_obj also accepts a flat duck-typed stand-in (number, num_pages,
has_previous, has_next, previous_page_number, next_page_number) for a view
that does not use Django's own Paginator (TBL-004, icvoss/django-brickwork#217).
{% endcomment %}
{% include "brickwork/components/_pagination.html" with page_obj=invoices %}
</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/_data_table.html |
A records or definition table, sortable, selectable, responsive. |
brickwork/components/_filter_bar.html |
A form of filter fields above a list or table. |
brickwork/components/_page_header.html |
A page's title, description, and action row. |
brickwork/components/_pagination.html |
Page links below a list or table. |
brickwork/nav/_nav.html |
Not a catalogue component |
brickwork/shell/app.html |
The authenticated app shell: sidebar, topbar, and content region. |