]> # Changelog Newest first. This file is the single source of truth for the plugin's update notes: `build/make-standalone-plg.sh` embeds it verbatim into the `.plg`'s `CHANGES` block, so what you read here is what Unraid shows on update. ###2026.07.24c - Docs: add this changelog; the plugin's update notes are now the real per-version history instead of a fixed blurb. ###2026.07.24b - Repoint EVERY per-theme stylesheet on the page, not just Unraid's core theme. Plugins ship their own per-theme stylesheet under two link conventions — "themes/NAME.css" (core webGUI, Community Applications) and "style-NAME.css" (Docker manager, Unassigned Devices). Toggling now follows all of them, so the Apps grid AND the Unassigned Devices / Docker tables track light/dark instead of stranding stray white table rows or invisible cards on the dark page. - Fix updates silently keeping the old files. Unraid's plugin-manager skips an inline file whose target already exists, so previous fixes never reached an already-installed box on update. The plugin now wipes its web files first and rewrites them fresh every install/update. ###2026.07.24 - Sync the Community Applications (Apps tab) theme with the toggle. Its cards no longer stay dark-on-dark (invisible) when you switch to dark mode. ###2026.07.23a - Dark mode: darken the top-nav menu strip so it no longer stays light with a light active-tab underline in the white/black theme family. - Recolour the Unraid Connect header items (server name, notification bell, account dropdown) so they stay legible when a forced theme disagrees with Connect's own light/dark scheme, in every theme and mode. (Thanks @Pross.) ###2026.06.29 - Initial release: one-click light/dark toggle (sun/moon) in the Unraid header toolbar, with three modes — Auto (follows your OS colour scheme live), Light and Dark. - Instant client-side switch: no page reload, no server settings changed, per browser. - Forces a fully dark header and keeps command-execution output readable in dark mode, which Unraid's stock dark themes don't fully cover. ]]> rm -rf &emhttp; mkdir -p &emhttp; on every Unraid page (Menu="Buttons"). It loads the * global switcher script, which defines the ThemeSwitch() function the toolbar * anchor calls (onclick="ThemeSwitch();return false;"). */ ?> ]]> on every page (via ThemeSwitch.page, Menu="Buttons"). * No server writes, no page reload: it repoints the theme and the * Theme-- colour class in the browser, per client. * * mode (localStorage 'themeswitch-mode'): auto | light | dark (default auto) * - auto : follow the OS via prefers-color-scheme, reacting live * - light/dark : manual override within the current theme's layout family * * Themes split into two layout families that must not be mixed: * top-nav : white (light) <-> black (dark) * sidebar : azure (light) <-> gray (dark) * We auto-derive the pair from whatever theme the server rendered, so there is * no config and the page layout never changes on toggle. */ (function () { 'use strict'; var STORAGE_KEY = 'themeswitch-mode'; var THEMES = ['white', 'azure', 'black', 'gray']; // theme name -> { light, dark } pair for that theme's layout family var FAMILY = { white: { light: 'white', dark: 'black' }, black: { light: 'white', dark: 'black' }, azure: { light: 'azure', dark: 'gray' }, gray: { light: 'azure', dark: 'gray' } }; function themeLink() { // Constrain to the active stylesheet so a future preload/icon link to the same // path can't be picked up instead of the real theme . return document.querySelector('link[rel~="stylesheet"][href*="/styles/themes/"]'); } // Unraid's core theme is one , but several plugins ship their OWN per-theme // stylesheet, emitted server-side for the theme active at render, so client-side // toggling leaves them on the pre-toggle theme (Community Applications' Apps grid, // Unassigned Devices' + Docker manager's tables, ...). The token appears in two link // conventions: `.../themes/.css` (core webGUI, Community Applications) and // `.../style-.css` (dynamix.docker.manager, Unassigned Devices). Match either // so we repoint EVERY per-theme stylesheet, not just Unraid's own chrome. var THEME_TOKEN = /(\/(?:themes\/|style-))(white|azure|black|gray)\.css/; // Repoint one stylesheet to the resolved theme, preserving the token flavour // (themes/ vs style-) and its ?v= cache-buster (an opaque key, harmless; self-heals on // the next full page load). No-op on absent links or hrefs without a theme token. function swapThemeHref(link, theme) { if (!link) return; var href = link.getAttribute('href'); var next = href.replace(THEME_TOKEN, '$1' + theme + '.css'); if (next !== href) link.setAttribute('href', next); } // Parse the active theme name (white|azure|black|gray) from the , // or null if this Unraid version uses a layout we do not recognise. function currentTheme(link) { var m = link && link.getAttribute('href').match(/themes\/(white|azure|black|gray)\.css/); return m ? m[1] : null; } function getMode() { var m; try { m = localStorage.getItem(STORAGE_KEY); } catch (e) { m = null; } return (m === 'light' || m === 'dark') ? m : 'auto'; } function setMode(m) { try { localStorage.setItem(STORAGE_KEY, m); } catch (e) { /* private mode: in-memory only */ } } function prefersDark() { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } // Resolve the theme to display given the mode and the family pair. function resolveTheme(mode, pair) { if (mode === 'light') return pair.light; if (mode === 'dark') return pair.dark; return prefersDark() ? pair.dark : pair.light; // auto } // CSS injected only while a dark theme is active. Fixes Unraid's stock dark themes // don't cover: // // 1. Header bar — stock dark themes give it a LIGHT background with DARK content. // Re-scope the tokens the header colours its dynamix-side content from, plus the // inherited `color`, to light values within #header. // 2. Command-execution output (docker run, plugin installs) — Unraid prints it into // .CMD/.logLine/#logBody, which stay dark on the dark page. Force them light. // 3. Top-nav menu strip (#menu > .nav-tile) — in the black theme the stock strip stays // light (#f2f2f2) with dark labels and a dark active-tab underline (both read // var(--header-text-color)). It mounts OUTSIDE #header, so darken the strip and // re-scope that token here so the labels AND the .nav-item.active::after underline // flip light together. Harmless in the gray sidebar family (no #menu strip). var DARK_MODE_CSS = '#header{' + '--header-background-color:var(--mild-background-color);' + '--inverse-text-color:var(--text-color);' + '--header-text-color:var(--text-color);' + '--customer-header-text-color:var(--text-color);' + 'color:var(--text-color);' + '}' + '.logLine,fieldset.CMD,fieldset.CMD>legend,#logBody{color:var(--text-color)!important;}' + '#menu,.nav-tile{background:var(--mild-background-color)!important;--header-text-color:var(--text-color)!important;}'; // Unraid Connect header island (.unapi / UNRAID-USER-PROFILE) — server name, notification // bell, account dropdown/hamburger. Reachable light-DOM (not a closed shadow root), so we // can recolour it; it mounts OUTSIDE #header, so the rules are global and injected in both // modes. We resolve the colour ourselves rather than via var(--header-text-color): Connect // REDEFINES that token inside its own island from its independent (OS-driven) scheme, so // referencing it tracks Connect, not the header backdrop, and the items vanish whenever our // forced mode disagrees with Connect. // // The backdrop behind the island is DARK in every theme except azure, so light text is // right almost everywhere: nav-top (white/black) has a dark header band in both modes, and // in dark mode the #header darkening above darkens the gray sidebar backdrop too. Only // azure (sidebar + light mode) keeps a light backdrop and needs dark text. function islandCss(theme) { var color = (theme === 'azure') ? '#1d1b1b' : '#f2f2f2'; return '.unapi{--color-header-text-primary:' + color + '!important;}' + '.text-header-text-primary{color:' + color + '!important;}'; } // Our own