Overview
Generated Chirp apps own their visual identity.chirp newcopies ordinary CSS
and a small preference helper into the project — not a token registry, CSS-in-Python
API, or frontend build. The contract matches
RFC 025.
What the scaffold writes
static/css/
tokens.css # semantic custom properties
base.css # reset + element defaults
components.css # reusable component styles
patterns.css # product compositions
pages.css # route-level layout
static/js/
theme.js # progressive enhancement (no Alpine)
interactions.js # empty hook for app scripts
theme.py # cookie preference helpers
pages/_context.py # exposes theme (+ current_path) to layouts
Filenames are scaffold convention. Rename or merge them freely; Chirp does not error on different CSS paths.
Root theme attributes
Layouts render the preference on<html>:
<html lang="en" data-theme="{{ theme }}">
Allowed data-theme values: light, dark, system(default when unset).
Optionaldata-skin and data-densityare extension points — define allowlists
intheme.pybefore emitting them.
First paint does not need an inline script. The server setsdata-themefrom
the cookie, andtokens.css maps data-theme="system"through
prefers-color-scheme. That keeps CSP script-src free of unsafe-inline.
Preference persistence
| Posture | Authority |
|---|---|
| Anonymous | Same-sitechirp_theme cookie (HttpOnly, SameSite=Lax) |
| Authenticated (later) | Prefer an account setting that still drives the same root attribute |
Local storage is only a cross-tab sync hint intheme.js. Navigation output must
not depend on a client-only preference.
The no-JavaScript path is a plain form POST to/theme(CSRF-protected) that
sets the cookie and redirects. With script allowed,theme.jsupdates
data-themeimmediately on radio change, then the same form persists it.
HTMX swaps target page regions, not<html>, so root theme state survives
boosted navigation. Full reloads re-read the cookie.
Customize ownership
- Edit token values in
static/css/tokens.css. - Style components next to
templates/components/incomponents.css. - Keep product vocabulary in
patterns.css/templates/patterns/. - Extend
SKINS/DENSITIESintheme.pyonly with values you validate.
Do not render user-controlled CSS text. Prefer allowlisted attribute values or a generated stylesheet with fixed property names.
Accessibility
Scaffold CSS keeps:focus-visible, forced-colors, and
prefers-reduced-motionusable. Theme changes must not rely on color alone —
the control exposes text labels (Light / Dark / System).
Related
- CLI —
chirp newflags - chirp-ui — optional compatibility theme path
- Accessibility