HistorianGateway has no local block AND no Bootstrap button call sites (all 15 buttons use the kit's .rail-btn chassis class), so the kit's .btn rule matches nothing and bumping it changes nothing visible. My previous correction claimed the opposite. Checking this needs two greps — declaration AND call sites — which come apart exactly when an app doesn't use Bootstrap buttons.
7.3 KiB
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
<details>/<summary> disclosure, so the chrome works unchanged in static Blazor SSR
with only Bootstrap CSS present (no Bootstrap collapse JS bundle required).
Adopt
-
Reference the NuGet package in your app; keep your own Bootstrap 5
<link>inApp.razor. -
In
App.razor<head>, after your Bootstrap link, add<ThemeHead />:<link rel="stylesheet" href="..." /> @* Bootstrap — yours, not vendored by the kit *@ <ThemeHead /> -
Make your
MainLayouta thin delegate toThemeShell—@layoutcannot pass parameters, soThemeShellis a regular component andMainLayoutis a 3-line wrapper:@* Layouts/MainLayout.razor *@ @inherits LayoutComponentBase <ThemeShell Product="OtOpcUa" Accent="#2f5fd0"> <Nav> <NavRailSection Title="Navigation"> <NavRailItem Href="/" Text="Overview" Match="NavLinkMatch.All" /> <NavRailItem Href="/clusters" Text="Clusters" /> </NavRailSection> </Nav> <RailFooter>@* session info / sign-out link *@</RailFooter> <ChildContent>@Body</ChildContent> </ThemeShell>
Add @using ZB.MOM.WW.Theme to your _Imports.razor so all components are available
without per-file usings.
Login page
Use <LoginCard> 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 <AntiforgeryToken/> inside the card and validate ReturnUrl
server-side before redirecting (open-redirect risk):
<LoginCard Product="OtOpcUa" Action="/auth/login" ReturnUrl="@safeReturnUrl" Error="@errorMsg">
<AntiforgeryToken />
</LoginCard>
Components
| Component | Parameters (key) | Notes |
|---|---|---|
ThemeHead |
— | Emits <link> tags for theme.css and layout.css. Place in <head> 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 <NavLink class="rail-link">. |
NavRailSection |
Title*, Expanded (default true), ChildContent |
CSS-only collapsible <details> 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 <AntiforgeryToken/> 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
# 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