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.
6 · Font — bring your own
Nexigrid ships font-free: the default typeface is the native system stack (system-ui → San Francisco / Segoe UI / Roboto) exposed as --ng-font-base — zero download, no layout shift. To use your own font, load it and put it first in the base stack:
/* Nexigrid is FONT-FREE: default = native system stack, via --ng-font-base:
system-ui, -apple-system, "Segoe UI", Roboto, sans-serif (0 download, 0 CLS). */
/* Want your own font (e.g. Inter)? 1) load it, 2) put it first in the stack: */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
}
:root {
--ng-font-base: 'Inter', system-ui, -apple-system, sans-serif;
}
One override on --ng-font-base cascades to headings and body everywhere. (This site itself runs on the system stack.)