@supercat1337/color-scheme
v1.0.4
Published
Browser-only color scheme manager for Bootstrap 5 with dark/light mode auto-detection, localStorage persistence, and zero config — no init functions needed.
Downloads
295
Maintainers
Readme
@supercat1337/color-scheme
A tiny, fast, and easy-to-use color scheme and color theme management system for JavaScript applications that works with Bootstrap 5 and supports dark and light themes.
Browser‑only – This module is designed exclusively for the browser. It relies on DOM APIs (
window,document,localStorage,sessionStorage,matchMedia). Importing it in a server environment (Node.js, SSR) will throw an explicit error. For SSR frameworks, use dynamic imports on the client side.
Overview
The module gives you a simple way to manage color schemes in your application. It reads the user's system preference, allows users to override it, and automatically applies the corresponding theme to the DOM. The module works seamlessly with Bootstrap 5 via the data-bs-theme attribute.
Terminology: Scheme vs Theme
Color Scheme (
scheme) – Represents the user’s or system’s preference for a light or dark appearance. Possible values:"dark","light", or"auto"(for preferred scheme). Schemes are stored and managed by*SchemeStorageclasses.Color Theme (
theme) – Represents a concrete CSS theme name applied to the DOM via thedata-bs-themeattribute. The theme is a string (e.g.,"dark","light","custom-dark"). It determines which set of CSS variables or Bootstrap styles are active.
The CurrentSchemeStorage resolves the current scheme (from preferred/system) and then maps it to a theme name using getDefaultTheme(), which returns darkThemeName or lightThemeName. This separation allows you to:
- Let users choose
"auto"without worrying about theme names. - Use any custom theme name (e.g.,
"dark-blue") while keeping scheme logic unchanged.
Storage Architecture – How it works
The module uses three independent storage classes that work together:
SystemSchemeStorage– reads the OS/browser preference (prefers-color-scheme) and listens for changes.PreferredSchemeStorage– stores the user's explicit choice ("dark","light", or"auto") inlocalStorageand synchronises across tabs.CurrentSchemeStorage– resolves the actual active scheme by combining:- A manually overridden value stored in
sessionStorage(if any). - Otherwise, the preferred scheme (if not
"auto"). - Otherwise, the system scheme.
- A manually overridden value stored in
Resolution rule:
sessionStorage override → preferred (non-auto) → system → "light" (fallback)Important: CurrentSchemeStorage automatically subscribes to changes in both SystemSchemeStorage and PreferredSchemeStorage. Whenever they change, the current scheme is re‑evaluated and, if changed, emits a current-scheme-change event. This ensures "auto" mode works correctly without external glue code.
To apply a theme (e.g., Bootstrap's data-bs-theme), subscribe to currentSchemeStorage.onSchemeChange() and call applyColorTheme(currentSchemeStorage.getDefaultTheme()).
Installation
npm install @supercat1337/color-schemeUsage
Quick start (recommended)
Import the ready‑to‑use global singletons. No initialisation function needed – the module automatically sets up everything when imported in a browser.
import {
currentSchemeStorage,
preferredSchemeStorage,
systemSchemeStorage,
applyColorTheme,
} from '@supercat1337/color-scheme';
// The current scheme is already applied to the document (data-bs-theme attribute).
// You can read it:
console.log(`Current scheme: ${currentSchemeStorage.scheme}`); // "dark" or "light"
// Listen for changes (e.g., to update a toggle switch)
currentSchemeStorage.onSchemeChange(scheme => {
console.log(`Scheme changed to: ${scheme}`);
// update your UI if needed
});
// Toggle dark/light mode with a switch
const switcher = document.querySelector('#darkmode-switch');
switcher.checked = currentSchemeStorage.scheme === 'dark';
switcher.addEventListener('change', () => {
const newScheme = switcher.checked ? 'dark' : 'light';
currentSchemeStorage.scheme = newScheme; // changes the active scheme
preferredSchemeStorage.scheme = newScheme; // remember user's choice across sessions
});
// Get system preference
console.log(`System prefers: ${systemSchemeStorage.scheme}`);Custom theme names
If you need theme names other than the default "dark" and "light" (for example, "dark-blue", "light-gray"), you cannot use the global singletons – they are pre‑configured with the defaults. Instead, create your own instances:
import {
SystemSchemeStorage,
PreferredSchemeStorage,
CurrentSchemeStorage,
applyColorTheme,
addMetaThemeColor,
} from '@supercat1337/color-scheme';
const system = new SystemSchemeStorage();
const preferred = new PreferredSchemeStorage();
const current = new CurrentSchemeStorage(system, preferred, {
darkThemeName: 'dark-blue',
lightThemeName: 'light-gray',
});
// Add meta tags manually (optional)
addMetaThemeColor('#FFFFFF', '#212529');
// Apply theme on changes
current.onSchemeChange(() => {
applyColorTheme(current.getDefaultTheme());
});
// Initial application
applyColorTheme(current.getDefaultTheme());
// Later, you can destroy the instances if needed:
// system.destroy();
// preferred.destroy();
// current.destroy();Note: The exported global singletons (systemSchemeStorage, preferredSchemeStorage, currentSchemeStorage) are managed automatically. Do not call destroy() on them.
API Reference
Global singletons (available immediately on import)
| Singleton | Type | Description |
| ------------------------ | ------------------------ | ---------------------------------------- |
| systemSchemeStorage | SystemSchemeStorage | System (OS) preference |
| preferredSchemeStorage | PreferredSchemeStorage | User preference (stored in localStorage) |
| currentSchemeStorage | CurrentSchemeStorage | Resolved active scheme |
SystemSchemeStorage
get scheme(): "dark"|"light"– current system scheme.onSchemeChange(callback: (scheme) => void): () => void– subscribe to changes.destroy(): void– remove internal listeners (only needed for manual instances).
PreferredSchemeStorage
get scheme(): "dark"|"light"|"auto"set scheme(value)– update and persist preference.onSchemeChange(callback): () => voiddestroy(): void
CurrentSchemeStorage
darkThemeName: string– theme name used for dark mode.lightThemeName: string– theme name used for light mode.get scheme(): "dark"|"light"set scheme(value: "dark"|"light"|"auto")– sets the current scheme. If"auto", removes any session override and re‑evaluates from preferred/system.getDefaultTheme(): string– returnsdarkThemeNameorlightThemeNamebased on current scheme.onSchemeChange(callback): () => voiddestroy(): void
Utility functions
applyColorTheme(theme: string): void– setsdata-bs-themeattribute on<html>.applyColorThemeToElement(element: HTMLElement, theme: string): void– setsdata-bs-themeon a specific element.addMetaThemeColor(lightColor?: string, darkColor?: string): void– addstheme-colormeta tags with media conditions.getSystemPreferredColorScheme(): "dark"|"light"– returns current system scheme (read‑only).onSystemColorSchemeChange(callback): () => void– low‑level subscription to system changes.
Important notes
- Browser only – The module throws an error when imported in Node.js or any environment without
window/document. - No initialisation required – As soon as you import the module in a browser, it automatically:
- Adds the global style
:root { color-scheme: light dark; }. - Adds
theme-colormeta tags (light:#FFFFFF, dark:#212529). - Creates the global singletons and starts listening to system/preferred changes.
- Applies the initial theme to
document.documentElement.
- Adds the global style
- SSR workaround – If you use Next.js, Nuxt, SvelteKit, or any SSR framework, you must import the module only on the client side:
// In a client‑side hook (e.g., useEffect, onMounted) useEffect(() => { import('@supercat1337/color-scheme').then(({ currentSchemeStorage }) => { // use currentSchemeStorage }); }, []); - Persisting user choice – Always set
preferredSchemeStorage.schemetogether withcurrentSchemeStorage.schemeif you want the choice to survive page reloads. "auto"behaviour – SettingcurrentSchemeStorage.scheme = "auto"removes thesessionStorageoverride and re‑enables automatic following of preferred/system changes.
Browser support
- Modern browsers with ES2022+ (private class fields,
CSSStyleSheet,matchMedia,localStorage/sessionStorage). - Works with Bootstrap 5 (via
data-bs-theme).
