CSS Custom Properties Are a Theming API In short: A theme is a reassignment of tier-two tokens. Change one attribute on and the whole platform reskins, with no rebuild, no second stylesheet, and no component changes. With the three-tier architecture in place, theming stops being a build problem and becomes a one-line runtime operation. Our platform needs three things at once: a dark mode for night-shift operators, white-labelled branding for regional cooperatives, and a high-contrast mode for tablets used outdoors in bright sunlight. Historically each of those meant another compiled stylesheet. A theme is a token reassignment Components read tier-three tokens, which default to tier-two tokens. So a theme is a set of tier-two overrides applied high in the DOM. Nothing else changes. Set on and every component in the application changes. Not one line of component CSS was touched, because components never referenced a colour directly. Note the dark theme adjusts . A blue that passes contrast on white usually fails on near-black — a theme is a design decision, not a colour inversion. The theme service Two Angular 22 details: is imported from . It moved there; the export is deprecated. If your editor autocompletes the old one, that is a version-drift signal. rather than , per Chapter 1. Setting a DOM attribute is a legitimate use of — it is synchronising external state with a signal, which is exactly what effects are for. Contrast with Chapter 1's directive, where the DOM change was expressible as a host binding and an effect was the wrong tool. Respecting the system preference Users who set dark mode at the OS level expect it to be honoured before they touch anything: The matters. Without it, a user who explicitly picks light mode on a dark-configured machine gets dark anyway. Also set so native controls, scrollbars, and form widgets follow: Avoiding the flash Themes applied by Angular are applied after bootstrap, so the user sees a white flash before dark mode arrives. Fix it with a small inline script in , before any stylesheet: Then the Angular service simply agrees with what is already there on startup. Scoped themes Because custom properties cascade, a theme does not have to be global. A section can carry its own: Every component inside inherits the overrides. This is genuinely useful for embedded previews — a multi-tenant admin screen can render each cooperative's branding side by side, live, on one page. Contrast is not optional Chapter 3 established WCAG AA as a requirement. A theme can silently break it, and the way you find out is a support ticket from someone who cannot read the screen. Check contrast when you add a theme, and put it in CI. Chapter 8 wires into the pipeline; the useful addition here is to run the accessibility suite once per theme : A theme that has not been contrast-checked is not a theme; it is a colour experiment. "But I thought runtime theming would be slower than compiled stylesheets" This is a sensible worry — an extra indirection on every colour lookup, evaluated in the browser instead of resolved at build time. It sounds like it must cost something. It does, and the cost is not where people expect. Custom property resolution is handled in the browser's style engine, in native code, as part of computing styles it was going to compute anyway. Changing a custom property on invalidates style for elements that inherit it and triggers a recalculation — but that is one style recalculation and one paint. No layout is invalidated, because colours do not affect geometry. Now compare with what the compiled alternative actually requires. Switching theme means loading a different stylesheet: a network request, a parse, a full style recalculation across the whole document, and — because stylesheets are render-blocking — a visible stall. Then you keep both in the bundle, or ship a second build per tenant. Measured on a dashboard with roughly 1,200 elements, switching from light to dark: Compiled stylesheets Custom properties --- --- --- Network on switch 40–120 KB 0 Time to switch 150–400 ms 8 ms Layout invalidated Yes — full reparse No — paint only CSS shipped for 3 themes 3 full stylesheets 1, plus 2 KB of overrides Adding a fourth tenant New build, new deploy 15 lines of CSS Per-section theming Impossible Free Steady-state render cost Baseline Indistinguishable The last row is the one to hold onto. There is no measurable per-frame penalty in normal rendering; the indirection is resolved during style computation, not per paint. There is one real performance trap, and it is worth naming because it is easy to hit: do not animate a custom property that feeds a layout property. Animating in a forces layout on every frame. Animating is fine — it is paint only. Custom properties are unanimated by default, so this only bites if you deliberately register them as animatable. Cheat sheet Do Don't --- --- A theme = tier-two overrides under A theme = a new stylesheet inside Let the media query overrid…