A lightweight, vanilla JavaScript utility for multi-theme management. Toggle between themes by applying a prefixed class to <html> — perfect for CSS variable‑based theming.
- Zero dependencies – pure vanilla JS
- Small footprint – ~5KB minified
- Framework agnostic – works anywhere
- Accessible – works with screen readers (demos include ARIA labels)
- Persistent – saves user preference to
localStorageorsessionStorage
You define themes in your CSS using a class prefix (e.g., .theme-ocean, .theme-sunset). ThemePicker applies the corresponding class to <html> based on user selection or stored preference.
/* Your CSS */
.theme-ocean {
--bg: #0ea5e9;
--text: #082f49;
}
.theme-sunset {
--bg: #f97316;
--text: #7c2d12;
}Copy themePicker.js into your project.
cp themePicker.js your-project/const picker = new ThemePicker({
themes: {
ocean: { name: 'Ocean' },
sunset: { name: 'Sunset' }
},
defaultTheme: 'ocean',
storageType: 'local',
onThemeChange: (newTheme, oldTheme) => {
console.log(`${oldTheme} → ${newTheme}`);
}
});The library does not provide UI components. You build the interface that fits your project.
// Example: bind a button
document.querySelector('#ocean-btn').addEventListener('click', () => {
picker.applyTheme('ocean');
});
// Example: bind a dropdown
themeSelect.addEventListener('change', (e) => {
picker.applyTheme(e.target.value);
});.theme-ocean {
--bg: #0ea5e9;
--text: #082f49;
}
.theme-sunset {
--bg: #f97316;
--text: #7c2d12;
}
body {
background: var(--bg);
color: var(--text);
}| Option | Type | Default | Description |
|---|---|---|---|
themes |
object |
(required) | Theme definitions. Each key must have a name property. |
classNamePrefix |
string |
'theme-' |
Prefix for CSS classes. Final class: ${prefix}${themeKey} |
storageKey |
string |
'selectedTheme' |
Key used in localStorage/sessionStorage |
storageType |
string |
'local' |
'local', 'session', or 'none' (no persistence) |
defaultTheme |
string |
first key in themes |
Fallback when no stored preference exists |
onThemeChange |
function |
null |
Callback fired when theme changes. Receives (newTheme, oldTheme) |
Applies a theme by key. Returns true if successful, false if theme not found.
picker.applyTheme('sunset');Returns the currently active theme key.
const current = picker.getCurrentTheme(); // 'ocean'Resets to the default theme (as defined in defaultTheme).
picker.resetToDefault();Cleans up event listeners. Reserved for future features (cross‑tab sync). Call if removing the instance in an SPA.
picker.destroy();storageType |
Behavior |
|---|---|
'local' |
Persists indefinitely across browser restarts |
'session' |
Persists within a tab; clears when tab closes |
'none' |
No persistence; resets to default on each load |
Modern evergreen browsers (Chrome, Firefox, Safari, Edge). Requires:
classListlocalStorage/sessionStorage- ES6 (can be polyfilled)
ThemePicker applies the saved theme (i.e. localStorage or sessionStorage if specified in constructor) immediately when initialized. To prevent a flash of the wrong theme:
If you use a single .js file for initialization and UI rendering:
Place the script in <head> with no defer/async, and wrap UI-building code in DOMContentLoaded:
<head>
<script src="theme-picker.js"></script>
<script>
const picker = new ThemePicker({ ... });
document.addEventListener('DOMContentLoaded', () => {
// Build your UI here
});
</script>
</head>See the /demos directory for working examples of this pattern.
If you split into two files (recommended for complex UIs):
theme-init.js— initialize ThemePicker. Place in<head>(no defer/async).ui-widget.js— build your theme picking UI. Place before</body>or withdefer.
The /demos directory contains complete, copy‑pasteable examples:
- Dropdown – compact select menu
- Button Grid – playful emoji buttons
- Radio Group – native HTML radios, fully accessible
- Segmented Control – iOS‑style pill control
Open static demos/index.html to browse all demos.
ThemePicker itself does not render UI, but the included demos demonstrate accessible patterns:
- Semantic HTML (
fieldset,legend,button,label) - ARIA labels on interactive elements
- Keyboard navigation support
When building your own UI, ensure buttons and controls have appropriate aria-label or visible text.
The class design allows:
- Multiple instances (if needed)
- Per‑instance callbacks
- Cleaner encapsulation
- Easier testing
MIT