3.9 KiB
UI Theming — switchable themes + "cockpit" dark skin
Context
The current UI is functional but flat and generic. The user wants to try a different look (reference: a dark "cockpit" dashboard mockup — deep navy surfaces, cyan/amber accents, glow on the live reading, photo banner) without losing the ability to switch back.
Today there is no theming layer at all: 0 CSS custom properties, ~430 hardcoded colours across 16 CSS files. So the switch has to be built before any new skin can exist.
Key finding that reduces scope: Main.jsx already has the card structure
the mockup shows — .weighing-section containing .weight-card elements for
Tare / Gross / Scale / Net, each with header, value and meta. No restructuring
of the detail panel is needed; this is a styling job.
Approach
Token layer + data-theme attribute on <html>. Two themes:
classic— reproduces today's look exactly (the safe default)cockpit— the new dark skin
1. Token layer
New frontend/src/styles/tokens.css, imported once in index.css:
:root, :root[data-theme="classic"] {
--bg, --surface, --surface-raised, --border,
--text, --text-muted, --accent, --accent-contrast,
--success, --warning, --danger,
--radius, --shadow-card, --glow-accent
}
:root[data-theme="cockpit"] { /* same names, dark values */ }
Token NAMES are semantic (--surface), never literal (--navy), so a third
theme is one block.
2. Convert existing CSS to var()
Mechanical replacement of hardcoded colours. classic values are lifted from
the current CSS so the default theme is visually unchanged — that is the
acceptance test.
3. Theme switch
ThemeContext(mirrors the existingAuthContext/NomenclatureContextpattern infrontend/src/contexts/)- Sets
data-themeondocument.documentElement - Persists to
localStorage, wrapped in try/catch (private mode can throw) - Toggle control in
Header.jsx - Default:
classic— nothing changes for operators unless they opt in
4. Cockpit theme + banner
Dark palette, raised card surfaces, cyan accent, glow on the live reading,
monospace weight readouts. Banner strip behind the vehicle title in
.vehicle-detail — image asset TBD (CSS gradient placeholder until the user
supplies one). Banner is cockpit-only and must collapse on short viewports;
this may run on a weigh-station monitor.
Staging
Deliberately two stages so a wrong palette is caught after 3 files, not 16.
Stage 1 — tokens, ThemeContext, header toggle, and convert:
frontend/src/components/Main.css(68 colours)frontend/src/components/Header.css(17)frontend/src/components/ScaleMonitor/ScaleMonitor.css(47)frontend/src/index.css,frontend/src/App.css
→ user reviews both themes on the main weighing screen.
Stage 2 — remaining 13 files, once the direction is approved: NomenclatureManager (79), NomenclatureManagementOverlay (54), LicenseManager (45), ChangePasswordOverlay (22), ExtraCombinationsTable (20), DataDisplay (19), Login (15), NomenclatureEntryForm (14), NomenclatureDropdown (14), FormOverlay, ReportPreviewModal.
Critical files
frontend/src/styles/tokens.css(new)frontend/src/contexts/ThemeContext.jsx(new)frontend/src/components/Header.jsx— toggle controlfrontend/src/App.jsx— wrap in ThemeProviderfrontend/src/components/Main.css— the main surface
Report editor CSS (components/ReportEditor/) is out of scope: the A4
designer renders a paper page that must stay white regardless of theme.
Verification
npx vitest run— 201 tests still pass- Classic theme vs
git stashscreenshot: visually identical - Toggle switches instantly, no reload; survives refresh (localStorage)
- Cockpit: check contrast on the live reading, balanced/unbalanced states, and that the banner collapses on a short viewport
- Real browser check of both themes on the main weighing screen