# The stylesheet: source tree, layers, build `cascade.css` is generated from `styles/` and is not in git. This page covers how the tree is organised, how the cascade is kept disciplined, how the build works, and how to prove a CSS change did what you meant. Token names and values: [design-system.md](design-system.md). The rules a patch must follow: [conventions.md](conventions.md). ## One directory per cascade layer ``` luci-theme-footstrap/ build-css.sh styles/ 00-header.css banner + the single @layer declaration 02-tokens.css @layer tokens private --fs-* tier + the --*-color-* export tier 03-palettes.css @layer tokens palettes (tokens only) 04-nocolormix.css @layer tokens static twins for the 36 color-mix() tokens base/ @layer base widget defaults the views count on 10-reset 20-typography 30-forms 40-tables 50-chrome 60-modal 70-buttons 90-widgets 95-luci theme/ @layer theme footstrap's own components and layouts 10-chrome 15-wallpaper 16-login-bg 20-shell 25-progressbar 30-tables 35-alerts 40-tabs 45-misc 50-toplayout 55-buttons 60-inputs 65-dropdown 70-modal 75-search 90-responsive 95-a11y-media 97-print pages/ @layer page per-page corrections 10-login 20-overview 30-software 40-sshkeys 50-leases 60-assoclist 70-syslog 80-appearance 90-processes ``` Concatenation order is `styles/` → `base/` → `theme/` → `pages/`, and inside each directory the numeric prefix is the order. `base/` came out of one 2300-line file. The split was purely mechanical: not a single rule moved within the layer, and the computed-style diff was zero — that was the condition for calling it done. Palettes are split across two files on purpose: `03-palettes.css` holds only token definitions and lives in the `tokens` layer, while rules like `:root[data-wallpaper="pattern"] .fs-main { background-color: … }` are ordinary styles and would lose to `theme` from inside `tokens`. They live in `theme/15-wallpaper.css`. The directory cannot be called `src/` — to `luci.mk` that means C sources. ## Layers ```css @layer tokens, base, theme, page; ``` A later layer beats an earlier one **regardless of selector specificity**. So a `theme` rule never has to outrank a `base` rule by specificity or by `!important`. The unlayered level outranks every layer and is deliberately left empty — it is the escape hatch. `node.css`, which LuCI attaches for individual pages after the theme, also lands there. ### Layer order is fixed by the FIRST mention of a name, and that can be hijacked `@layer tokens, base, theme, page;` holds the order only while `cascade.css` is the **first sheet in the document to name a layer**. A name met earlier becomes the *first* — that is, the weakest — layer, and everything declared afterwards stacks above it. If a foreign sheet carrying `@layer theme` lands ahead of ours, the order becomes `theme, tokens, base, page`, and `base`, where `* { padding: 0 }` lives, starts beating the entire chrome. Measured on a router: `.fs-content` loses its `24px 28px`, and the bar, tabs and buttons collapse with it. Not hypothetical — it is the flip side of the re-host in `fs-sheets.js`: we wrap a foreign sheet in `@layer theme`, but *where* the package inserted it is the package's choice. Ace (pulled in by `luci-app-ssclash` and any package with an editor) puts its `