# T34 Spike: Theme Dark-Mode Feasibility Findings **Date:** 2026-06-18 **Task:** Verify whether the external `ZB.MOM.WW.Theme` shell can go dark via `data-bs-theme` CSS override. --- ## 1. CSS Load Order Confirmed from `src/ZB.MOM.WW.ScadaBridge.Host/Components/App.razor`: ``` bootstrap.min.css (line 8) bootstrap-icons.css (line 9) (line 10) → injects theme.css then layout.css Host.styles.css (line 11) site.css (line 12) ← our override point ``` `site.css` loads **after** theme.css and layout.css. A `[data-bs-theme="dark"]` block in site.css has sufficient specificity to shadow every `:root` token defined in theme.css, because `[attr]` selector specificity (0,1,0) beats `:root` (0,0,1) at equal cascade layer. --- ## 2. Token Inventory (theme.css `:root`) All tokens defined in the `:root` block of `~/.nuget/packages/zb.mom.ww.theme/0.3.1/staticwebassets/css/theme.css`: ### Surfaces & ink | Token | Light value | Role | |---|---|---| | `--paper` | `#f4f4f1` | page background | | `--card` | `#ffffff` | raised surfaces, bars, table heads | | `--ink` | `#1b1d21` | primary text | | `--ink-soft` | `#5a6066` | secondary text, labels | | `--ink-faint` | `#8b9097` | tertiary text, captions | | `--rule` | `#e4e4df` | hairline borders, row dividers | | `--rule-strong` | `#d2d2cb` | emphasised hairlines, bar underline, pills | ### Accent | Token | Light value | Role | |---|---|---| | `--accent` | `#2f5fd0` | links, sort arrows, primary actions | | `--accent-deep` | `#1e3f99` | hover/pressed accent | ### Status — foreground | Token | Light value | |---|---| | `--ok` | `#2f9e44` | | `--warn` | `#e8920c` | | `--bad` | `#e03131` | | `--idle` | `#868e96` | ### Status — tinted backgrounds | Token | Light value | |---|---| | `--ok-bg` | `#e9f6ec` | | `--warn-bg` | `#fdf1dd` | | `--bad-bg` | `#fceaea` | | `--idle-bg` | `#eef0f2` | ### Status — borders | Token | Light value | |---|---| | `--ok-border` | `#c6e6cd` | | `--warn-border` | `#efd6a6` | | `--bad-border` | `#eec3c3` | | `--ok-border-soft` | `#bfe3c6` | | `--warn-border-soft` | `#f0d9ab` | | `--bad-border-soft` | `#f0c0c0` | ### Warning ink | Token | Light value | |---|---| | `--warn-ink` | `#b56a00` | | `--warn-ink-deep` | `#8a5a00` | ### Info tint | Token | Light value | |---|---| | `--info-bg` | `#e7ecfb` | | `--info-border` | `#cdd9f7` | ### Neutral surface washes | Token | Light value | Role | |---|---|---| | `--zebra-bg` | `#fbfbf9` | even-row / sticky-head fill | | `--hover-bg` | `#f3f6fd` | row / rail-link hover | | `--active-bg` | `#eef2fc` | active rail-link fill | ### Bootstrap overrides (derive from above) | Token | Value | |---|---| | `--bs-body-bg` | `var(--paper)` | | `--bs-body-color` | `var(--ink)` | | `--bs-body-font-family` | `var(--sans)` | | `--bs-body-font-size` | `0.9rem` | | `--bs-primary` | `var(--accent)` | | `--bs-border-color` | `var(--rule)` | | `--bs-emphasis-color` | `var(--ink)` | --- ## 3. Rail Tokenization Audit (layout.css) Every colour-bearing rule in the side-rail uses CSS custom properties. No hard-coded hex or rgb values appear anywhere in layout.css. Key rules: **Rail background and border:** ```css .side-rail { background: var(--card); border-right: 1px solid var(--rule-strong); } ``` **Brand block:** ```css .side-rail .brand { color: var(--ink); border-bottom: 1px solid var(--rule); } .side-rail .brand .mark { color: var(--accent); } ``` **Section eyebrow / toggle:** ```css .rail-eyebrow-toggle { color: var(--ink-faint); } .rail-eyebrow-toggle:hover { color: var(--ink); } .rail-section > summary::before { color: var(--ink-faint); } ``` **Nav links (default / hover / active):** ```css .rail-link { color: var(--ink-soft); } .rail-link:hover { background: var(--hover-bg); color: var(--ink); } .rail-link.active { background: var(--active-bg); border-left-color: var(--accent); color: var(--accent-deep); } ``` **Rail foot / session block:** ```css .rail-foot { border-top: 1px solid var(--rule); } .rail-btn { color: var(--ink-soft); background: var(--card); border: 1px solid var(--rule-strong); } .rail-btn:hover { border-color: var(--accent); color: var(--accent); } .rail-roles { color: var(--ink-faint); } .rail-ico { color: var(--ink-faint); } ``` **Login title:** ```css .login-title { color: var(--ink); } ``` --- ## 4. VERDICT **RAIL TOKENIZES → dark feasible by token override.** Every rail background, text, and border colour in layout.css resolves through a CSS custom property from the token set. Overriding the token values under `[data-bs-theme="dark"]` in site.css (which loads after theme.css/layout.css) will fully recolour the side-rail, app chrome, panels, data tables, chips, and status elements — no Theme-package coordination required. --- ## 5. Concrete Dark Token Override List Paste this entire block under `[data-bs-theme="dark"]` in site.css. Accent colour (`--accent`) is kept at `#4d7fe8` — a lighter shade of the brand blue that reads well on dark backgrounds while preserving brand identity. ```css [data-bs-theme="dark"] { /* Surfaces & ink */ --paper: #1b1d21; --card: #25282d; --ink: #e6e8ec; --ink-soft: #9aa0aa; --ink-faint: #6b727c; --rule: #33373e; --rule-strong: #3a3f46; /* Accent — lightened for contrast on dark */ --accent: #4d7fe8; --accent-deep: #7aa0ef; /* Status — foregrounds unchanged (inherently visible on dark) */ --ok: #40b856; --warn: #f0a030; --bad: #f05050; --idle: #7a8290; /* Status — tinted backgrounds (dark surface tints) */ --ok-bg: #1a2e1e; --warn-bg: #2e2210; --bad-bg: #2e1616; --idle-bg: #25282d; /* Status — borders on dark tints */ --ok-border: #2a5030; --warn-border: #4a3510; --bad-border: #4a2020; --ok-border-soft: #264a2c; --warn-border-soft: #44300e; --bad-border-soft: #44201e; /* Warning ink — brightened amber for dark contrast */ --warn-ink: #d4891a; --warn-ink-deep: #b8741a; /* Info tint */ --info-bg: #1a2240; --info-border: #2a3660; /* Neutral surface washes */ --zebra-bg: #202328; --hover-bg: #2a2e38; --active-bg: #1e2640; /* Bootstrap bridge tokens */ --bs-body-bg: var(--paper); --bs-body-color: var(--ink); --bs-border-color: var(--rule); --bs-emphasis-color: var(--ink); } ``` --- ## 6. Caveats 1. **Bootstrap intrinsic dark utilities**: Bootstrap 5.3 has its own `[data-bs-theme="dark"]` rules for components (alerts, badges, modals, etc.). Our token block sits in site.css at cascade level 0; Bootstrap's own dark-mode block in bootstrap.min.css (loaded earlier) will also fire. The Bootstrap rules and our token rules are additive and complementary — they should not conflict, but any Bootstrap component that ignores our tokens and uses Bootstrap's built-in dark-mode palette may have a slightly different feel. Inspect on first run. 2. **`body` gradient in theme.css**: The `body` rule in theme.css is: ```css body { background: radial-gradient(1200px 480px at 88% -8%, var(--card) 0%, rgba(255,255,255,0) 70%), var(--paper); } ``` On dark mode, `rgba(255,255,255,0)` in the gradient is transparent white and will remain unchanged. The gradient will still render; `var(--card)` will resolve to the dark card value and the effect will be a subtle lighter sheen in the top-right corner — acceptable, but the gradient can be suppressed in site.css for a flatter dark background if preferred. 3. **Status tints on dark**: The status background tokens (`--ok-bg`, `--warn-bg`, `--bad-bg`) are deliberately dark-surface equivalents (very low-saturation tints). Verify contrast ratios of status chip text against these backgrounds before shipping. 4. **`data-bs-theme` placement**: The attribute must be set on `` (or ``) so the CSS selector matches all descendants. The toggle implementation (next task) should write `document.documentElement.setAttribute('data-bs-theme', 'dark')`.