Theming · Light, Dark & Custom
Themes are driven entirely by design tokens. Switch with a single data-theme attribute on <html>, toggle from JS via ng.theme, or define your own brand theme by overriding --ng-* variables. No duplicate CSS.
1 · The data-theme attribute
Light is the default; dark is built in. Set the theme declaratively on the root element — every --ng-* token adapts, no extra stylesheet.
<!-- on the <html> tag -->
<html data-theme="dark"> … </html>
2 · Switch from JavaScript — ng.theme
The ng_theme.js module exposes a small API on window.ng.theme. set() persists the choice; apply() only previews it.
// window.ng.theme (requires ng_theme.js)
ng.theme.set('dark'); // set + persist (null = auto)
ng.theme.get(); // 'auto' | 'light' | 'dark' | '<name>'
ng.theme.toggle(); // light <-> dark
ng.theme.apply('light'); // preview only, no persist
Wire it to any control — e.g. a toggle button:
<button onclick="ng.theme.toggle()">Toggle theme</button>
3 · React to changes — ng:theme:change
Every change dispatches ng:theme:change on <html>, with the new value in detail.theme.
document.documentElement.addEventListener('ng:theme:change', (e) => {
console.log(e.detail.theme); // 'light' | 'dark' | '<name>' | null
});
4 · Persistence (no flash)
The choice is stored in localStorage under ng-theme and restored automatically. To avoid a flash of the wrong theme on load, set the attribute before the stylesheet, in the <head>:
<!-- in <head>, BEFORE the stylesheet — prevents the flash -->
<script>try{document.documentElement.setAttribute('data-theme',
localStorage.getItem('ng-theme')||'light');}catch(e){}</script>
5 · Custom theme (your brand)
A custom theme is just a named data-theme that overrides the design tokens you care about. Define it in CSS, then activate it by name.
/* a named custom theme = override design tokens */
[data-theme="ocean"] {
--ng-primary-bg: #0e7490;
--ng-primary-hover: #0c6378;
--ng-surface: #0b1f24;
/* …only the --ng-* tokens you need */
}
ng.theme.set('ocean'); // activate the custom theme
Override only the tokens you need (brand colors, surfaces). Everything else inherits the framework defaults. Interactive theme switching requires ng_theme.js in your foot.