# ZB.MOM.WW.Theme Shared Technical-Light UI kit for the ZB.MOM.WW SCADA family: design tokens + IBM Plex fonts (static web assets) and a canonical side-rail shell + widgets. The kit ships one .NET 10 Razor Class Library (RCL) with CSS custom-property tokens, the three IBM Plex woff2 fonts, and side-rail layout CSS — all served from `_content/ZB.MOM.WW.Theme/…` — plus a set of Blazor SSR components that carry no inline colours and reuse the token classes. Bootstrap 5 is **not** vendored; each app keeps its own Bootstrap link. The kit ships **no JavaScript**: every interactive affordance — the narrow-viewport side-rail hamburger and the collapsible nav sections — is a native CSS-only `
`/`` disclosure, so the chrome works unchanged in static Blazor SSR with only Bootstrap **CSS** present (no Bootstrap collapse JS bundle required). ## Adopt 1. Reference the NuGet package in your app; keep your own Bootstrap 5 `` in `App.razor`. 2. In `App.razor` ``, **after** your Bootstrap link, add ``: ```razor @* Bootstrap — yours, not vendored by the kit *@ ``` 3. Make your `MainLayout` a thin delegate to `ThemeShell` — `@layout` cannot pass parameters, so `ThemeShell` is a regular component and `MainLayout` is a 3-line wrapper: ```razor @* Layouts/MainLayout.razor *@ @inherits LayoutComponentBase @* session info / sign-out link *@ @Body ``` Add `@using ZB.MOM.WW.Theme` to your `_Imports.razor` so all components are available without per-file usings. ### Login page Use `` for the sign-in form. The card posts to a server endpoint (`/auth/login` by default) so `SignInAsync` can run before the response starts. You **must** inject `` inside the card and **validate `ReturnUrl` server-side** before redirecting (open-redirect risk): ```razor ``` ## Components | Component | Parameters (key) | Notes | |---|---|---| | `ThemeHead` | — | Emits `` tags for `theme.css` and `layout.css`. Place in `` after Bootstrap. | | `ThemeShell` | `Product`*, `Accent`, `Logo`, `Nav`, `RailFooter`, `ChildContent` | Side-rail chassis. Not a layout — delegated to from `MainLayout`. `Accent` overrides `--accent` token. | | `BrandBar` | `Product`*, `Logo` | Brand glyph + product name; rendered inside `ThemeShell`'s rail header. | | `NavRailItem` | `Href`*, `Text`*, `Icon`, `Match` | Wraps ``. | | `NavRailSection` | `Title`*, `Expanded` (default `true`), `ChildContent` | CSS-only collapsible `
` group; no JS, works in static SSR. | | `StatusPill` (`StatusState`) | `State`* (`Ok`/`Warn`/`Bad`/`Idle`/`Info`), `ChildContent` | Inline chip. `StatusState` enum is in `ZB.MOM.WW.Theme`. | | `LoginCard` | `Product`*, `Action` (default `/auth/login`), `ReturnUrl`, `Error`, `ChildContent` | Static form-POST sign-in card. Inject `` via `ChildContent`; validate `ReturnUrl` server-side. | | `TechButton` (`ButtonVariant`) | `Variant` (`Primary`/`Secondary`/`Danger`/`Ghost`), `Type`, `Busy`, `ChildContent`, splatted attrs | `Busy` disables the button and shows a spinner. `ButtonVariant` enum is in `ZB.MOM.WW.Theme`. | | `TechCard` | `Title`, `Header`, `ChildContent`, `Footer`, `Class` | Panel with optional head/body/footer slots. | | `TechField` | `Label`*, `Hint`, `Error`, `ChildContent` | Form field wrapper with label, hint text, and inline error. | \* `EditorRequired` parameter. Flat namespace: all components and enums live in `ZB.MOM.WW.Theme`. One `@using` covers everything. ## Static assets Served at `_content/ZB.MOM.WW.Theme/…` by the ASP.NET static-web-asset pipeline: | Path | Contents | |---|---| | `css/theme.css` | Design tokens (`--accent`, `--ok`, `--warn`, …), typography, utility helpers | | `css/layout.css` | Side-rail shell layout, collapsible nav, `StatusPill` variants, card/field helpers, Bootstrap `.btn` sizing | **Bootstrap button sizing (new in 0.4.0).** The kit now normalizes `.btn` / `.btn-sm` against its 0.9rem body via `--bs-btn-*` variable overrides, because Bootstrap hard-codes `--bs-btn-font-size: 1rem`. Several consuming apps had written this same block into their own stylesheets; **trim the local copy when you bump to 0.4.0** — trim rather than delete, because a local `.btn` may also carry app-specific *shape* declarations the kit does not ship. **This is not universal, and checking it takes two greps, not one.** An earlier version of this note claimed all five consumers carried the block. They do not — and the follow-up correction, that the missing-block app would therefore be *restyled* by the bump, was also wrong. | Question | Check | |---|---| | Does the app declare its own `.btn` sizing? | `grep bs-btn-font-size`, excluding `lib/bootstrap` | | Does the app **use** Bootstrap buttons at all? | count `class="…"` values carrying a bare `btn` token | HistorianGateway answers **no to both** — all 15 of its buttons use the kit's own `.rail-btn` chassis class — so the kit's `.btn` rule matches nothing there and bumping it changes nothing visible. The first question alone is misleading in both directions: "no local block" does **not** imply "the kit now supplies sizing the app was missing." Size buttons by overriding the `--bs-btn-*` variables, never by redeclaring box properties — the `.btn-group` seam and radius machinery reads those variables, so a literal `font-size` at equal specificity silently reduces `.btn-sm` to a padding-only difference. Rail-footer actions: `class="rail-btn"` is the inline default; add `rail-btn-block` (`class="rail-btn rail-btn-block"`) for a full-width footer button. Use the modifier rather than redeclaring `.rail-btn` in an app stylesheet — a local override that sets `width: 100%` overflows the rail, because the width resolves against the containing block and the inline version's `0 0.6rem` side margins are then added on top. | `fonts/ibm-plex-sans-400.woff2` | IBM Plex Sans Regular | | `fonts/ibm-plex-sans-600.woff2` | IBM Plex Sans SemiBold | | `fonts/ibm-plex-mono-500.woff2` | IBM Plex Mono Medium | `theme.css` declares `@font-face` with `url('../fonts/…')` — correct relative path from `css/` to `fonts/`. (OtOpcUa's original `url('fonts/…')` was a latent 404; the kit fixes it.) All colours are CSS custom-property tokens declared once in `theme.css`'s `:root` block — no hardcoded hex appears outside it. Derived border/hover/wash shades (e.g. `--ok-border`, `--warn-ink`, `--info-bg`, `--hover-bg`, `--active-bg`, `--zebra-bg`) are named tokens too, so a token-block edit re-themes the whole kit. ## Build ```bash # from ZB.MOM.WW.Theme/ dotnet build -c Release # TreatWarningsAsErrors — expect 0 warnings dotnet test # 38 bUnit tests ./build/pack.sh # → ./artifacts/ZB.MOM.WW.Theme.0.1.0.nupkg ```