Thanks for helping improve the kit. This guide covers the repo layout, the local dev setup and the build/release steps.
django_cotton_ui/ # the Python package (this ships on PyPI)
├── templates/cotton/ui/ # component templates (the c-ui.* components)
└── static/cotton-ui/ # BUILT assets — do not edit by hand (see below)
├── cotton-ui.css # the stylesheet (copied from src/css)
├── cotton-ui.js / .esm.js # the Alpine bundle (built from src/js)
└── cotton-ui.min.js[.map] # minified bundle
src/ # frontend source (NOT shipped)
├── css/cotton-ui.css # the stylesheet — SOURCE OF TRUTH
├── js/ # Alpine component source, entry: js/cotton-ui.js
├── build.js # esbuild bundler config
└── package.json # npm scripts
pyproject.toml # Poetry project / packaging
Two things are easy to get wrong:
django_cotton_ui/static/cotton-ui/is generated. Never edit those files directly. Editsrc/css/cotton-ui.cssandsrc/js/**, then rebuild (below). The build copies the CSS and bundles the JS into the package, and those built files are committed so the wheel ships them.- The package directory is
django_cotton_ui(underscore); the repo and the distribution aredjango-cotton-ui(hyphen).
- Python ≥ 3.8 and Poetry
- Node ≥ 18 and npm
- Familiarity with Tailwind CSS v4 and Alpine.js v3 (the kit is built on both)
poetry installAll frontend tooling lives in src/.
cd src
npm install # first time only
npm run build # bundle JS + copy CSS into django_cotton_ui/static/cotton-ui/While developing, use the watcher (rebuilds the JS bundle on change; the CSS is plain CSS so it's just copied):
npm run watchnpm run build produces, in django_cotton_ui/static/cotton-ui/:
cotton-ui.css— copied verbatim fromsrc/css/cotton-ui.csscotton-ui.js/cotton-ui.esm.js— the Alpine component bundlecotton-ui.min.js(+.map) — minified buildmanifest.json
Commit the rebuilt static/ files along with your src/ changes, the package ships
them.
The kit has no standalone dev server, you render it from a host project. Two ways:
The django-cotton docs site serves both the core and UI kit docs with live examples for every component, and it's already wired for kit development: a Docker Compose stack with a Django server, Redis and a Tailwind v4 watcher that scans the kit's templates. It bind-mounts this repo, so your edits show up without reinstalling anything.
-
Check out the core repo (it holds the docs project) as a sibling of this one:
<parent>/ ├── django-cotton/ # core repo; docs project lives under docs/ └── django-cotton-ui/ # this repo -
From the core repo, build the image and start the stack:
docs/docker/bin/build.sh # add 'mac' on Apple Silicon: build.sh mac docs/docker/bin/run-dev.sh # docker compose up
-
Open http://localhost:8002.
How edits propagate:
- Templates under
django_cotton_ui/templates/are bind-mounted, so they show on reload, and the Tailwind watcher recompiles utility classes on save. - Built assets: the docs import the kit's compiled
static/cotton-ui/(both the CSS and the Alpine bundle), so after editingsrc/css/**orsrc/js/**, rerunnpm run build(ornpm run watch) here to regenerate them.
Install the package into any Django project configured with the Tailwind v4 build (scan the
templates with @source, import cotton-ui.css, load the Alpine bundle), exactly the setup
in the installation guide.
The kit is styled with Tailwind v4 and compiles from @source-scanned template source, so:
- No runtime-interpolated arbitrary classes. A class like
max-w-[{{ var }}]never reaches the compiler. Use a fixed class, a CSS variable (max-w-[var(--x)]) or an inlinestyle. - Theme via tokens. Surfaces, radius, shadows, the focus ring and the accent are all CSS
variables (see the theming guide). Prefer a token
over a hardcoded colour so components stay themeable. Structural surfaces use the
boxrole (--radius-box,--color-box-border,--shadow-box); form controls usecontrol. - Django comments are single-line.
{# … #}must stay on one line, a multi-line one leaks into the HTML. Use{% comment %}…{% endcomment %}for multi-line notes. - Keep new components accessible (labels, roles, focus management) and matching the existing
component API patterns (
c-varsprops, named slots,{{ attrs }}passthrough). - Arbitrary attrs reach the value control. So a Livewire-style framework binding
(
lace:model,wire:model,x-model,data-*) set on a component tag reaches the element that submits the value, put{{ attrs }}on the underlying<input>/<select>/<textarea>. When the value lives in a hidden control behind a visible root (checkbox, radio, switch, combobox), declareclassin<c-vars>so{{ class }}styles the root and the class-free{{ attrs }}binds the hidden control (a bare{{ attrs }}next to a hardcodedclassproduces a duplicateclassattribute and silently drops one). Covered bytests/integration/test_attribute_forwarding.py.
Tests live in django_cotton_ui/tests/ and render the real component templates through the
cotton loader (no fixtures). Run the whole suite from the repo root:
python runtests.py
# or a single module / test:
python runtests.py django_cotton_ui.tests.integration.test_attribute_forwardingIt uses Django's built-in runner with django_cotton_ui/tests/settings.py, so the only
requirements are django and django-cotton (already dependencies) — no pytest needed.
- Bump the version in both
pyproject.tomlandsrc/package.json(keep them in sync). cd src && npm run build, then commit the rebuiltstatic/assets.- Commit and push the version bump.
- Publish: run the "Publish to PyPI (manual bump)" workflow from the GitHub Actions tab
(
workflow_dispatch). It builds with Poetry and publishes using thePYPI_TOKENsecret.
Pre-1.0 versioning follows the 0.x convention: breaking changes bump the minor (0.1 → 0.2), fixes bump the patch (0.1.0 → 0.1.1).