Skip to content

Install and adopt

Install

pip install abilian-ds        # or: uv add abilian-ds

The package carries the stylesheet, the Jinja macros, the linter and the token reference. It has no front-end toolchain and does not want one.

Vendor the stylesheet

abilian-ds sync

That writes 17 files into static/css/ads/:

static/css/ads/
├── ads.css          # the one a page links
├── knobs.css        # the overridable surface
├── derive.css       # everything computed from it
├── base.css         # the reset
├── situ.css         # only if you use situ
└── parts/           # alert, button, card, content, form, indicator,
                     # layout, nav, overlay, shell, table, forced-colors

Then link the entry sheet — the @imports pull in the rest:

<link rel="stylesheet" href="/static/css/ads/ads.css">
Why vendored rather than served from the package?

A browser needs a URL, and ads.css reaches its parts through relative @imports, so the directory shape has to survive the copy.

Vendoring normally opens an escape hatch — someone edits the local copy and the project quietly forks. abilian-ds lint closes it by hashing every vendored file against the packaged original, so an edit is a finding:

static/css/ads/parts/button.css:0: asset-drift: edited locally;
    change it upstream and re-run `abilian-ds sync`

Configure the project

Paths live in pyproject.toml, so the commands take no flags:

[tool.abilian-ds]
templates = ["templates"]          # what to lint
theme = "static/css/ads"           # where sync put the stylesheet
components = "templates/ads"       # where sync put the macros
allow = ".abilian-ds-allow"        # project allowlist, optional
app = "myapp.main:app"             # optional, enables the endpoint check

Configure Jinja

from jinja2 import StrictUndefined

TemplateConfig(
    directory=[HERE / "templates"],
    engine=JinjaTemplateEngine,
    engine_callback=lambda engine: setattr(
        engine.engine, "undefined", StrictUndefined
    ),
)

StrictUndefined is not optional

It is what turns a misspelt component argument into an error instead of an unstyled element. {{ button("Save", variant="pirmary") }} raises; without it, it renders a button with no classes and nobody notices until a screenshot.

Run the linter

abilian-ds lint
templates/page.html:3: unknown-class: bg-gray-100: not in the design system
1 finding(s)

Green means on-system. Wire it into CI and into your pre-commit hook; it is the contract, and everything else in these docs is a description of it.

Next