Overview — what it does
The InfraGuard console ships with two themes: dark (default) and light. The theme is switched by a single attribute on the document root (<html>), data-theme, whose value swaps the entire set of --infr-* CSS variable tokens defined in SCSS. Every component uses var(--infr-*) tokens instead of hardcoded hex, so flipping one word (dark ↔ light) instantly re-paints the whole UI.
The default is dark — as a NOC (network operations center) console it uses a deep blue-black background with teal signal accents so severity colors (crit / warn / good) read crisply against a dark field.
<html data-theme="dark"> ← default, NOC dark
<html data-theme="light"> ← light (bright office environments)
The theme is a pure front-end preference. It is never stored on the server and has nothing to do with your account or the database — it lives only in the browser's
localStorage. It survives logout, and if someone else signs in with the same account on a different device, that device follows its own setting.
When to use
- Night operations / NOC wall displays → dark (less glare, higher severity-color contrast).
- Bright offices, projectors, doc screenshots → light.
- Personal contrast / color-vision preference — switch per user.
Once chosen it persists in that browser, so you normally set it only once.
Layout
Header toggle button
Both the console shell (layouts/app.blade.php) and the login screen (layouts/simple.blade.php) place a theme toggle button at the right of the top bar.
| Item | Value |
|---|---|
| Markup hook | <button data-theme-toggle> |
| Icon | mdi-theme-light-dark (half-moon) |
| aria-label | msg.action.theme (localized) |
| Action | Click cycles dark ↔ light |
| Position | Top-bar actions area (next to the language switcher, left of the user menu) |
The pre-login (guest) screen carries the same toggle, so you can change theme without authenticating.
Color token groups
resources/scss/_variables.scss defines the same token names with different values in two blocks: :root[data-theme="dark"] and :root[data-theme="light"]. Representative tokens:
| Token | Purpose | dark | light |
|---|---|---|---|
--infr-bg |
Page background | #0b1220 |
#f6f8fb |
--infr-surface |
Panel / card surface | #111b2c |
#ffffff |
--infr-fg |
Body text | #e9eff8 |
#111a28 |
--infr-fg-muted |
Secondary text | #8493ac |
#63728b |
--infr-border |
Borders | #1e2c43 |
#dbe2ec |
--infr-accent |
Accent (teal) | #2dd4bf |
#0d9488 |
--infr-crit |
Critical severity | #fb7185 |
#e11d48 |
--infr-warn |
Warning severity | #fbbf24 |
#b45309 |
--infr-good |
Healthy severity | #34d399 |
#0f9d6a |
--infr-info |
Info | #38bdf8 |
#0369a1 |
Beyond these, each theme also defines --infr-surface-2/hi, --infr-border-2, --infr-accent-soft/ink, --infr-on-accent (button text on accent), TCP state colors (--infr-state-*), chart colors (--infr-chart-2), shadow/glow (--infr-shadow / --infr-glow), and the top-bar glass (--infr-topbar-*). Each block also declares color-scheme: dark|light so native form controls and scrollbars match the theme.
The SSH terminal palette is an exception. The
--infr-term-*tokens stay a deep, immersive dark regardless ofdata-theme. Even in light theme the SSH console keeps a dark terminal.
How to use
Switching
- Click the half-moon icon (theme toggle) at the right of the top bar.
- It flips dark ↔ light immediately, and the choice auto-saves.
- On the next visit or a page reload, your last choice is restored.
Under the hood (developer reference)
The front end is wired in resources/js/app.js. Core logic:
const THEME_KEY = 'infr-theme'; // localStorage key
const get_theme = () => localStorage.getItem(THEME_KEY) || 'dark'; // default dark
const set_theme = (t) => {
document.documentElement.setAttribute('data-theme', t); // root attribute
localStorage.setItem(THEME_KEY, t); // persist
window.dispatchEvent(new CustomEvent('infr:theme', { detail: t })); // re-color charts
};
// wire the toggle button
document.querySelectorAll('[data-theme-toggle]')
.forEach(btn => btn.addEventListener('click',
() => set_theme(get_theme() === 'dark' ? 'light' : 'dark')));
Behavior summary:
| Step | What |
|---|---|
| Storage key | localStorage['infr-theme'] = "dark" | "light" |
| Applied to | data-theme attribute on document.documentElement (the <html>) |
| Default | dark (when localStorage is empty or unreadable) |
| Event | Each switch fires an infr:theme CustomEvent on window (detail = current theme) |
| Cross-tab sync | Listens for the storage event → other tabs in the same browser sync automatically |
Flash-of-unstyled-content (FOUC) prevention — pre-paint script
A tiny inline script at the very top of <head> (shared by app.blade.php and simple.blade.php, before the CSS @vite load) sets data-theme before any stylesheet applies. This removes the "dark→light flash" on reload.
<script>
(function () {
try { document.documentElement.setAttribute('data-theme',
localStorage.getItem('infr-theme') || 'dark'); }
catch (e) { document.documentElement.setAttribute('data-theme', 'dark'); }
})();
</script>
Even where
localStorageis blocked (incognito, third-party storage blocked, etc.), thecatchsafely falls back to dark.
Charts and editors follow the theme
- ECharts (
<metric-chart>/ echarts-kit): reads--infr-*tokens viagetComputedStyleto build chart colors, axes, and tooltips, and re-colors on theinfr:themeevent. Theis_dark()test isdata-theme !== 'light'(dark is the default). See Servers time-series charts. - CodeMirror (
<cm-editor>): the:darkprop toggles betweenoneDark(dark) and a light theme. Vue pages computeis_dark_mode = document.documentElement.dataset.theme === 'dark', pass it as:dark="is_dark_mode", and refresh it on theinfr:themeevent. See Backup Script Builder.
<cm-editor v-model="script.body" lang="bash" :dark="is_dark_mode"></cm-editor>
Data sources
The theme has no DB or server storage at all. It uses only:
| Location | Content |
|---|---|
localStorage['infr-theme'] |
Per-browser user choice (dark/light) |
<html data-theme> |
Runtime applied state |
resources/scss/_variables.scss |
Actual per-theme values of the --infr-* tokens (built into CSS) |
resources/js/app.js |
Toggle / persist / event wiring |
CLAUDE.md Appendix C (Light/Dark theme tokens) is the source of truth for the palette design.
Access
The theme is a UI preference unrelated to authentication or authorization.
- Every user, including guests (pre-login), can switch freely — no level gating.
- It behaves identically at demo=1 / staff=5 / admin=10.
- Because nothing is written server-side, it never appears in the audit log (
infr_audit_log).
Troubleshooting & FAQ
Q. The theme resets on reload.
If the browser blocks localStorage (incognito, strict privacy settings, third-party storage blocking), the choice can't be saved and it reverts to the default dark every time. Grant storage access for the site.
Q. I changed it in one tab but another tab didn't update.
The switch propagates to other tabs in the same browser via the storage event. If it doesn't reflect immediately, that tab may not have loaded the listener wiring (app.js) yet — reload it. Different devices/browsers have separate storage and do not sync (this is by design).
Q. I switched to light but the SSH terminal stays dark.
That's expected. The terminal uses a dedicated --infr-term-* palette that stays deep-dark regardless of the app theme (readability / immersion).
Q. One component looks wrong (the theme doesn't apply to it).
That component hardcoded hex instead of tokens. By convention components must use only var(--infr-*) tokens. Check for direct hex values in resources/scss/pages with the regression grep:
rg -n "#[0-9a-fA-F]{3,6}" resources/scss/pages
Q. I want it to follow my OS dark mode.
The current implementation does not follow the OS preference (prefers-color-scheme). The initial value is always dark, and it only remembers what the user explicitly picked with the toggle. OS integration is a roadmap item.
Related
- Navigation & layout — the top-bar actions area (theme / language / user menu)
- Language · locale — the other front-end preference alongside theme
- Using this manual — /manual search and structure
- Servers time-series & MetricChart — charts that re-color from theme tokens
- Backup Script Builder — CodeMirror
:darkbinding - SSH console — a fixed-dark terminal independent of the app theme
