brickwork
Beautiful defaults, proved by the examples. brickwork ships 42 examples
(16 pages, 26 sections) built from nothing but its own shipped tokens and
components. 41 of them add no CSS at all. The one that does is the date
range picker, whose scoped .bw-drp block uses only existing --bw-*
tokens, because brickwork ships no date picker component for it to compose
(see Example pages). Every one is readable in the repo, so
"the defaults are beautiful" is a claim you check by opening a file rather
than one you take on trust.
A brand-agnostic, app-facing professional UI substrate for server-rendered Django, on the ecosystem stack: Tailwind 4 (CSS-first), Alpine 3, HTMX 2, Django 6.0. It provides the application shell, navigation and active-route resolution, an accessible form-field renderer, and interaction primitives (modal, toast, dropdown, combobox, tabs, disclosure) wrapped behind stable Django components.
This is not a Django-admin skin. Applications provide data, permissions
and business behaviour; the substrate provides structure, presentation and
interaction conventions, on a professional, tested-accessible baseline: RTL
via logical properties, a real themeable dark-mode system, and four
composable theme axes (brand x theme x density x direction). Rebranding is
token-first: every visual value is a --bw-* custom property, so a consumer
rebrands by overriding tokens, never by touching component classes.
Accessibility is tested, not asserted by design. CI blocks every push on an axe-core WCAG 2.2 AA scan across 86 documents (43 fixtures x light and dark themes), plus a no-JS floor suite, keyboard suites, mobile-overflow checks at 320/360/375/414px, and pixel-level composited contrast measurement. That last check exists because the axe gate itself once ran green over a real 4.25:1 contrast defect (axe's contrast check does not rasterise the page, so text over a background image reports "incomplete" rather than a violation); the gate catching its own blind spot and adding a check for it is stronger evidence than a gate that has never missed.
Status: stable, on public PyPI at 3.5.1 (
pip install django-brickwork); CHANGELOG.md records the current release. The five semver-governed public-API contracts (token, template, navigation, interaction, JavaScript) are live. The surface covers the application shell and nav, the beautiful-by-default token system (elevation, state overlays, type roles, motion, borders, with fine colours derived live from a small load-bearing brand set viacolor-mix()), the interaction set (modal, toast, dropdown, combobox, tabs, disclosure, tooltip, slide-over), forms with the whole-form renderer and the HTMX 422 loop, the data table with sortable and selectable modes, the feedback and input-chrome primitives, the wizard/stepper, and a machine-readable token contract with a per-tenant brand-CSS emitter.See it running: brickwork.icvoss.com is the live interactive demo and template gallery, and icvoss.com/packages/django-brickwork hosts the package documentation page.
Marketing kit (1.2.0+). brickwork also ships an opt-in
brickwork.marketingsub-app (a marketing shell and nine marketing components: hero, feature grid, pricing tier, pricing table, CTA, testimonial, logo cloud, stat band, FAQ) on the same--bw-*token and accessibility contract, so a consumer can build its public marketing pages on brickwork alongside its console. Worked landing/pricing/about pages are shipped as copy-paste examples, not importable templates: see Example pages below.
Documentation
- docs/DESIGN.md: the canonical token reference; every
--bw-*name, default value, and derivation rule. - docs/BRANDING.md: how a consuming app brands brickwork (the load-bearing token minimum, dark mode, the four axes, the fg-on-accent contrast trap, and dynamic per-tenant / per-user theming recipes).
- docs/QUICKSTART.md: start here. Orients you, then routes you to the right guide below.
- docs/INTEGRATION.md: the greenfield integration cookbook, the seams a consuming app wires end to end (settings and static, nav config, context processor, a worked HTMX 422 form, the chrome/body boundary).
- docs/ADOPTION.md: the strangle guide for migrating an existing app onto brickwork cluster by cluster (multi-host, asset coexistence, the htmx floor).
- src/brickwork/examples/README.md: the copy-paste example pages, what each one is, and how to use one (see Example pages below).
- frontend/README.md: the in-repo build that compiles the shipped static assets.
Install
From public PyPI:
uv add django-brickwork # or: pip install django-brickwork
INSTALLED_APPS = [
"brickwork",
# ...
]
The compiled CSS and JS ship inside the package and are referenced with plain
{% static %}; no build-tool dependency (django-vite / django-tailwind) is
imposed on consumers. Consumers provide their own Alpine 3 +
@alpinejs/focus (and optionally htmx 2) via their own frontend build; brickwork
registers behaviour onto the host Alpine instance and never calls
Alpine.start().
Supported versions
| Dependency | Supported |
|---|---|
| Python | 3.12+ |
| Django | 6.0 (the CI-tested matrix; later majors are not yet asserted, and the dependency pin is deliberately floor-only) |
| htmx | >= 2.0 for the interaction contracts (see below); not required otherwise |
| Alpine.js | 3.x plus @alpinejs/focus, provided by the host app |
| Browsers | evergreen; the interaction suite is CI-tested on Chromium (Playwright) |
htmx floor: htmx >= 2.0
brickwork's interaction contracts (the HTMX 422 form-swap loop, toast delivery
via hx-swap-oob, modal dismissal via the HX-Trigger: bw:modal:close response
header, combobox server filtering) are built and CI-gated on htmx >= 2.0
only. htmx 1.9 is out of contract (BR-BW-HTMX-010): htmx 2 changed default
response handling in ways the 422 loop relies on, and the interaction suites only
ever exercise htmx 2. A brownfield app on htmx 1.9 should upgrade htmx to 2.x as a
prerequisite before adopting brickwork's interaction primitives; see
docs/ADOPTION.md.
Quickstart: a first console page
Five minutes from install to a themed, accessible console page. Wire the app and the shell context processor:
# settings.py
INSTALLED_APPS = [
"brickwork",
# ...
]
TEMPLATES = [{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
"OPTIONS": {"context_processors": [
# ... Django's defaults ...
"brickwork.context_processors.theme", # wires theme/density/dir onto <html>
"yourapp.context_processors.nav",
]},
}]
Declare the nav once, validated at import:
# yourapp/nav.py
from brickwork.models import NavItem
from brickwork.services.navigation import validate_nav_config
NAV = [
NavItem(label="Dashboard", url_name="dashboard"),
]
validate_nav_config(NAV)
# yourapp/context_processors.py
from brickwork.services.navigation import resolve_active_item, visible_items
from yourapp.nav import NAV
def nav(request):
return {
"nav_items": visible_items(NAV, request),
"nav_active": resolve_active_item(NAV, request),
}
Extend the shell and fill its blocks:
{# yourapp/templates/yourapp/dashboard.html #}
{% extends "brickwork/shell/app.html" %}
{% load brickwork_nav %}
{% block sidebar %}{% bw_nav nav_items nav_active %}{% endblock %}
{% block page_header %}
{% include "brickwork/components/_page_header.html" with title="Dashboard" %}
{% endblock %}
{% block content %}
{% include "brickwork/components/_empty_state.html" with heading="Nothing here yet" body="Create your first project to get going." %}
{% endblock %}
# yourapp/views.py
from django.shortcuts import render
def dashboard(request):
return render(request, "yourapp/dashboard.html", {"bw_page_title": "Dashboard"})
Run collectstatic and open the page: shell, sidebar nav with active-route
highlighting, skip link, dark mode and density axes, all on the default theme.
Branding it is a handful of --bw-* token overrides
(docs/BRANDING.md); the full seam-by-seam walkthrough is
docs/INTEGRATION.md.
Example pages
A whole page is the most project-specific thing you own, so brickwork does not
ship one as a template you extend. Instead it ships sixteen complete, working
pages built from its tokens, components and shells, as copy-paste examples in
src/brickwork/examples/ (base.html; app/list, detail, dashboard,
date-range-picker, form, wizard, settings, console, confirm;
auth/signin, signup, reset; marketing/landing, pricing, about).
Looking for a date picker component: there is no package-maintained one, and
there never will be (BR-BW-INPUT-004 is a Fixed rule: no bw_date_picker
tag, template or Alpine behaviour exists or ever will). That is not the same
as no date picker. app/date-range-picker.html is a full working date range
picker, calendar popover with weekday and month grids, locale-aware via
Django's own django.utils.dates, single-date mode included, over a native
<input type="date"> no-JS floor that stays the submitted control at all
times. Copy it; its header explains what your view must supply. What
brickwork declines to ship is the maintained JS calendar component, not the
capability: the copied page is yours outright, with no component contract for
brickwork to maintain or break.
They cannot be extended, by construction: the directory is package data, not
an app templates/ folder, so Django's APP_DIRS loader cannot see it and
{% extends "brickwork/examples/..." %} raises TemplateDoesNotExist. That
is deliberate (ADR-056): a page you import is a page a dependency can reshape
on your next pin bump; a page you copy is yours outright.
To use one: open it (in the repo, or via brickwork.examples.read_example()),
copy it into your own templates/ tree, and edit it. Each example is
annotated with what your view must supply and stays real, specific content
throughout, never Lorem ipsum. See
src/brickwork/examples/README.md for the
full list and how they are tested.
Extending a shell directly (brickwork/shell/app.html,
brickwork_marketing/shell/marketing.html, and friends) remains fully
supported and is the option that keeps receiving improvements automatically;
copying an example is the alternative for a project that wants to own its
page outright from day one.
Contracts
brickwork's public API is five versioned contracts: token, template, navigation, interaction (HTMX), and JavaScript (Alpine). Template block names, HTMX target IDs, Alpine component names, event names and token names are semver-governed. docs/DESIGN.md enumerates the token contract, docs/INTEGRATION.md walks the template, navigation and interaction seams, and CHANGELOG.md records every contract change release by release.
Usage
Tags vs includes
Some components are consumed as template tags, others via {% include %}.
This is deliberate: a component that carries logic (variant validation, a11y
enforcement, icon resolution) ships as a tag so that logic is not duplicated at
every call site; a purely structural component is an include the consumer fills
with context.
-
Tags (load the library first):
{% bw_icon %},{% bw_button %},{% bw_badge %},{% bw_alert %},{% bw_nav %},{% bw_nav_header %},{% bw_nav_rail %},{% bw_field_widget %}. The three nav tags are sibling renderers over the sameNavItemtree: the sidebar/tree render, the horizontal marketing-header row, and the compact two-tier rail (see INTEGRATION.md section 2).{% load brickwork_components brickwork_icons brickwork_nav %} {% bw_button label="Save" variant="primary" %} {% bw_badge label="New" variant="info" %}
The
_button.html/_badge.html/_alert.htmltemplate files exist but are the tags' own render targets, not a consumer-facing{% include %}API. Call the tag, not the partial. -
Includes (structure you fill with context):
_page_header.html,_data_table.html,_pagination.html,_empty_state.html,_filter_bar.html,_spinner.html, and the form partialsforms/_field.html/forms/_form_errors.html.{% include "brickwork/components/_data_table.html" with table_id="gadgets" columns=columns rows=rows %}
Record rows may include a
datamapping for consumer-owned hooks on the rendered row, for example{"data-item-id": gadget.pk}. Onlydata-*names are accepted; Brickwork's owndata-bw-*hooks remain reserved._stat.htmlaccepts the same optionaldatamapping on its tile root.
Icons: decorative or labelled, always
{% bw_icon %} requires exactly one of decorative=True or label="...",
and raises TemplateSyntaxError if given neither or both. This is intentional
(ICO-007, WCAG 4.1.2): an icon is either purely presentational (aria-hidden) or
carries meaning (an accessible name), never ambiguous.
{% bw_icon "search" decorative=True %} {# beside a visible label #}
{% bw_icon "trash" label="Delete item" %} {# standalone, meaningful #}
You rarely call bw_icon directly for the icon inside a bw_button or
bw_nav item: those tags take an icon="..." argument and handle the a11y
pairing for you. Reach for bw_icon directly only for a standalone icon in your
own markup, where this rule applies.
Development
Python package:
pip install -e ".[dev]"
pytest
Frontend build (compiles tokens + component assets into the package's static
dir; see frontend/README.md):
npm install
npm run build
Licence
MIT. See LICENSE.
Release files for django-brickwork 3.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_brickwork-3.6.0.tar.gz | 419.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_brickwork-3.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 791.0 kB
Release files / django_brickwork-3.6.0.tar.gz
| Download URL | django_brickwork-3.6.0.tar.gz |
|---|---|
| Size | 419.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
475b0458267a560934571fbf7b16ee9a5e80829e1c7a07aeec6e0245685356a4
|
|
BLAKE2b-256 checksum How to use checksums |
f0c15e42ec020fe374ac26b194077690a9fd6506b6e78c56c4d53413785b91fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.
Transparency logRelease files / django_brickwork-3.6.0-py3-none-any.whl
| Download URL | django_brickwork-3.6.0-py3-none-any.whl |
|---|---|
| Size | 371.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4df539da1291a281081cbbb1f9477df226c9fbcade6dff42f33c63868f6930e
|
|
BLAKE2b-256 checksum How to use checksums |
0718440b90ab11260a537884da0a378483a7d1253cfd235498df9e82c07f110e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.
Transparency log