mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
Appearance & theming
TREK's look is driven by design tokens (CSS custom properties) so a single per-user config can re-skin the whole app — color scheme, accent, transparency, text size, density — without touching component code. New pages get this for free if they follow the contract below.
How it works
- The per-user config is one validated blob (
AppearanceConfigin@trek/shared), stored under theappearancesettings key. applyAppearance()(src/theme/applyAppearance.ts) is the only code that writes styling to the DOM: it toggles.dark, setsdata-scheme/data-no-transparency/data-density/data-reduce-motionon<html>, and writes the custom-accent + type-scale CSS variables.- A render-blocking
public/theme-boot.jsreplays a cached snapshot before first paint to avoid FOUC (external classic script — the prod CSP blocks inline scripts). - Schemes are data: swatch metadata in
src/theme/schemes.ts, token values in the[data-scheme="…"]blocks insrc/index.css.
Token taxonomy
| Purpose | Token / utility |
|---|---|
| Surfaces | --bg-primary/secondary/tertiary/elevated/card/input/hover/selected · bg-surface* |
| Text | --text-primary/secondary/muted/faint · text-content* |
| Borders | --border-primary/secondary/faint · border-edge* |
| Accent | --accent (fill) · --accent-text (on fill) · --accent-on (on surface) · --accent-hover · --accent-subtle · bg-accent / text-accent-on / bg-accent-subtle |
| Status | --success/-soft, --danger/-soft, --warning/-soft, --info/-soft · bg-danger-soft text-danger etc. |
| Elevation | --shadow-sm/md/lg/card/elevated/modal/dropdown/popover · shadow-modal etc. |
| Overlay / inverse | --overlay · --bg-inverse / --text-inverse · bg-inverse text-inverse-text |
| Type tiers | text-title / text-subtitle / text-body / text-caption (scale with the user's per-tier multipliers) |
The contract (enforced by npm run theme:lint)
- Surfaces/text/borders use the semantic utilities (
bg-surface*,text-content*,border-edge*) orvar(--token)— never rawbg-slate-*/text-gray-*/bg-white. - The primary/"black" action look uses
bg-accent+text-accent-text(+ring-accent) — neverbg-slate-900/bg-black/bg-indigo-*. This is what lets a user's accent reach new surfaces automatically. - Semantic text size uses the tier utilities
text-title/subtitle/body/caption— never an inlinefontSize: <px>, never rawtext-sm/text-xsto mean a tier. - Translucent/blurred surfaces consume an alpha/glass token
(
--bg-elevated,--tooltip-bg,--glass-*) or abackdrop-blurutility, so transparency-off can neutralize them centrally — never inlinergba()backgrounds orbackdrop-filterin JSX. - Components never read
AppearanceConfigto compute styles. OnlyapplyAppearancereads the config and writes tokens; components read tokens. Dark state is read off the.darkclass, never duplicated into React state. - New appearance dimensions are added to
AppearanceConfig+applyAppearance+schemes.ts— never a second applier or a parallel token family. New feature palettes derive their accent fromvar(--accent).
Allowed to stay inline / literal
Genuinely dynamic values (data-driven colors like cat.color, computed
geometry/transforms/sizes), and the surfaces CSS variables can't reach: injected
map popup/marker HTML, Mapbox/MapLibre paint, and the standalone @react-pdf
documents. Mark intentional exceptions with a theme-lint-disable line comment.