Metadata-Version: 2.4
Name: django-brickwork
Version: 3.2.1
Summary: Brand-agnostic, app-facing professional UI substrate for server-rendered Django (Tailwind 4, Alpine 3, HTMX 2). Accessible by construction; not a Django-admin skin.
Author: Nigel Copley
License-Expression: MIT
Project-URL: Homepage, https://github.com/icvoss/django-brickwork
Project-URL: Documentation, https://github.com/icvoss/django-brickwork
Project-URL: Changelog, https://github.com/icvoss/django-brickwork/blob/main/CHANGELOG.md
Project-URL: Issue Tracker, https://github.com/icvoss/django-brickwork/issues
Project-URL: Source Code, https://github.com/icvoss/django-brickwork
Keywords: django,ui,design-system,tailwind,alpine,htmx,accessibility,wcag,components
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: User Interfaces
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: Django>=6.0
Provides-Extra: htmx
Requires-Dist: django-htmx>=1.28; extra == "htmx"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-django>=4.8; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: django-htmx>=1.28; extra == "dev"
Requires-Dist: ruff==0.15.22; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Dynamic: license-file

# brickwork

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.** Its value is the professional baseline:
WCAG 2.2 AA as a *tested* guarantee (axe-core in CI, not a claim), RTL via
logical properties, a real themeable dark-mode system, and four composable
theme axes (brand x theme x density x direction). Applications provide data,
permissions and business behaviour; the substrate provides structure,
presentation and interaction conventions.

> Status: **stable 1.x on public PyPI** (`pip install django-brickwork`);
> [CHANGELOG.md](CHANGELOG.md) records the current release. The five
> semver-governed public-API contracts (token, template, navigation,
> interaction, JavaScript) are live, and every component passes two hard gates
> in CI: is it accessible (axe-core WCAG 2.2 AA, light and dark themes) and is
> it beautiful by default. 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 via `color-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](https://brickwork.icvoss.com) is
> the live interactive demo and template gallery, and
> [icvoss.com/packages/django-brickwork](https://icvoss.com/packages/django-brickwork)
> hosts the package documentation page.
>
> **Marketing kit (1.2.0+).** brickwork also ships an opt-in
> `brickwork.marketing` sub-app (a marketing shell and eight marketing
> components: hero, feature grid, pricing tier/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](#example-pages) below.

## Documentation

- [docs/DESIGN.md](docs/DESIGN.md): the canonical token reference; every
  `--bw-*` name, default value, and derivation rule.
- [docs/BRANDING.md](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/INTEGRATION.md](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](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](src/brickwork/examples/README.md): the
  copy-paste example pages, what each one is, and how to use one (see
  [Example pages](#example-pages) below).
- [frontend/README.md](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
```

```python
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](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:

```python
# 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:

```python
# 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)
```

```python
# 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:

```django
{# 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 %}
```

```python
# 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](docs/BRANDING.md)); the full seam-by-seam walkthrough is
[docs/INTEGRATION.md](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 fifteen complete, working
pages built from its tokens, components and shells, as copy-paste examples in
`src/brickwork/examples/` (`base.html`; `app/list`, `detail`, `dashboard`,
`form`, `wizard`, `settings`, `console`, `confirm`; `auth/signin`, `signup`,
`reset`; `marketing/landing`, `pricing`, `about`).

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](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](docs/DESIGN.md) enumerates the
token contract, [docs/INTEGRATION.md](docs/INTEGRATION.md) walks the template,
navigation and interaction seams, and [CHANGELOG.md](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 same `NavItem` tree: the sidebar/tree render, the
  horizontal marketing-header row, and the compact two-tier rail (see
  [INTEGRATION.md](docs/INTEGRATION.md) section 2).

  ```django
  {% 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.html` template 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 partials `forms/_field.html`
  / `forms/_form_errors.html`.

  ```django
  {% include "brickwork/components/_data_table.html" with table_id="gadgets" columns=columns rows=rows %}
  ```

### 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.

```django
{% 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](LICENSE).
