A cozy, slate/blue-grey dark mode for the Harmony theme, built for Yeşilkart Forum. No pure black — soft, deep blue-greys in the spirit of Discord/GitHub dark modes, tuned for long reading sessions.
- Per-user toggle in Harmony's right sidebar and in the user menu (desktop + mobile), injected client-side — no template edits.
- Preference saved to
localStorage, applied instantly, no page reload. - First-time visitors follow their OS setting (
prefers-color-scheme), and keep following it live until they pick a mode explicitly. - Sets
html[data-theme="dark"](this plugin's CSS scope) andhtml[data-bs-theme="dark"](native Bootstrap 5.3 color mode) together. - Syncs
<meta name="theme-color">so mobile browser chrome matches. - Fires
action:darkmode.changed(client hook) so other plugins can react.
- NodeBB v3.2+ or v4.x (Bootstrap 5.3 based)
nodebb-theme-harmonyas the active theme- Designed for the default skin (no bootswatch skin). It will scope
itself under
data-theme="dark"regardless, but colors are tuned for Harmony's stock light palette.
cd /path/to/nodebb
npm install YesilkartForum/nodebb-plugin-darkmode # from GitHub
# or, once published: npm install nodebb-plugin-darkmode
./nodebb build
./nodebb restartThen activate Yeşilkart Dark Mode for Harmony in ACP → Extend → Plugins
(or ./nodebb activate nodebb-plugin-darkmode before the build).
Any change to the SCSS or client script requires
./nodebb buildand a restart.
There is no ACP page (yet). One site-wide setting exists, defaultMode,
which controls what visitors with no saved choice get:
| Value | Behaviour |
|---|---|
auto |
follow the visitor's OS setting (default) |
dark |
dark until the user toggles |
light |
light until the user toggles |
Set it directly in the database, e.g. MongoDB:
db.objects.updateOne(
{ _key: 'settings:darkmode' },
{ $set: { defaultMode: 'dark' } },
{ upsert: true }
);All colors live at the top of scss/darkmode.scss as SCSS variables
($dm-bg-body, $dm-text, $dm-link, …). Edit, rebuild, done. If you
change $dm-bg-body, also update DARK_BG in public/darkmode.js so the
mobile theme-color stays in sync.
The toggle script runs from NodeBB's bundle, so on a hard reload there can be a brief flash of light mode before it applies. NodeBB is a SPA, so this only affects the first page load. To remove it entirely, paste this into ACP → Appearance → Custom Content → Custom Header:
<script>
(function () {
try {
var m = localStorage.getItem('darkmode:mode');
if (m === 'dark' || (!m && matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.setAttribute('data-theme', 'dark');
document.documentElement.setAttribute('data-bs-theme', 'dark');
}
} catch (e) {}
})();
</script>(If you set defaultMode to light, drop the matchMedia clause; if
dark, replace the condition with m !== 'light'.)
If your logo doesn't read well on dark, add to the SCSS (or ACP custom CSS):
html[data-theme="dark"] [component="brand/logo"] {
content: url(/assets/uploads/system/site-logo-dark.png);
}plugin.json declares everything: scss bundles the stylesheet into the
site CSS, scripts bundles the client toggle. The only server hook is
filter:config.get (exposes defaultMode to the client). There are no
routes, sockets, or template overrides.
| Hook | Side | Purpose |
|---|---|---|
filter:config.get |
server | expose defaultMode to client config |
action:ajaxify.end |
client | (re)inject the toggle on navigation |
action:darkmode.changed |
client | fired by this plugin on mode change |
MIT © Yeşilkart Forum