Styling and Theming
Precompiled, never CDN
Tailwind is compiled ahead of time into static/dist/app.css. There is no
runtime JIT and no CDN script tag. The visual result is identical; the
difference is that classes Tailwind cannot see fail loudly in development
instead of silently in production, and pages ship one minified stylesheet
instead of a compiler.
npm install # once; pulls Tailwind, DaisyUI, Biome (devDependencies only)
make build-static # compile + fingerprint
In development you rarely need either: pages render without a build because
asset resolution falls back to source paths (see
Asset pipeline), and make serve runs a watcher that
rebuilds CSS on template changes.
Tailwind only emits classes it can see
tailwind.config.js scans app/components/web_frontend/templates/**/*.html
and static/js/**/*.js. A Tailwind class anywhere else (a Python string,
a new template directory) will not be in the compiled CSS. If you add a
template location, add its glob to content in tailwind.config.js.
The single rebrand point
Brand colors live in exactly one file: tailwind.config.js. The block is
marked in the file itself:
colors: {
aegis: {
bg: "#090B0D", // Page background
card: "#111418", // Card/surface background
border: "#272C36", // Borders, dividers
text: "#EEF1F4", // Primary text
muted: "#7E8A9A", // Secondary/muted text
teal: "#17CCBF", // Brand accent
amber: "#F59E0B", // Warning/highlight
},
},
These become utilities (bg-aegis-bg, text-aegis-teal,
border-aegis-border) used throughout the shipped templates. The DaisyUI
theme below the block mirrors the same values into DaisyUI's semantic slots
(primary, base-100, success), so component classes like btn and card
match the hand-written utilities. Change the block, rebuild, and the whole
frontend follows. Nothing else hardcodes a hex value.
The theme is selected in base.html via data-theme="aegis" on the <html>
element.
The layers
Three places styling can live, in order of preference:
- Utility classes in templates. The default. Most of the shipped markup is plain Tailwind utilities.
-
static/input.css,@layer components. For a pattern that repeats across templates when a Jinja macro would be overkill:Utilities are emitted after this layer, so a call site can still override a component class.
-
static/css/app.css. Hand-authored CSS that is not Tailwind at all. It ships with two rules worth knowing about:[x-cloak]{display:none}(hides elements until Alpine initializes them, preventing a flash of unstyled content on htmx swaps) andcolor-scheme: dark(tells the browser to render native controls dark).
Linting
make lint-frontend runs Biome over
static/js/ and djlint over the templates. Both are
scoped to the web frontend; neither touches the rest of the project. djlint
runs with the Jinja profile and a small, documented ignore list in
pyproject.toml ([tool.djlint]). make format-frontend reflows the JS with
Biome; it is opt-in because reformatting is noisy in diffs.