Components
st-theme-switcher
A theme management component that handles light, dark, and system preferences with localStorage persistence and cross-instance synchronization.
Basic Usage
Wrap your theme toggle buttons with <st-theme-switcher>. Use data-theme attributes on child elements to define which theme each button activates.
<st-theme-switcher>
<button type="button" data-theme="light">Light</button>
<button type="button" data-theme="dark">Dark</button>
<button type="button" data-theme="system">System</button>
</st-theme-switcher>
Icon Toggle Example
A common pattern is using icon buttons for the theme switcher:
<st-theme-switcher class="flex items-center gap-1">
<button type="button" data-theme="light" class="p-2 rounded hover:bg-neutral-100 dark:hover:bg-neutral-800">
<svg class="h-5 w-5"><!-- sun icon --></svg>
<span class="sr-only">Light mode</span>
</button>
<button type="button" data-theme="dark" class="p-2 rounded hover:bg-neutral-100 dark:hover:bg-neutral-800">
<svg class="h-5 w-5"><!-- moon icon --></svg>
<span class="sr-only">Dark mode</span>
</button>
<button type="button" data-theme="system" class="p-2 rounded hover:bg-neutral-100 dark:hover:bg-neutral-800">
<svg class="h-5 w-5"><!-- computer icon --></svg>
<span class="sr-only">System preference</span>
</button>
</st-theme-switcher>
How It Works
- Clicking a button with
data-theme="light|dark|system"updates the theme - Adds or removes the
darkclass on<html> - Persists the choice to
localStorageunder the keystellify.theme - Listens for system preference changes when set to "system"
- Synchronizes across multiple
<st-theme-switcher>instances on the same page
Theme Values
| Value | Behaviour |
|---|---|
| light | Forces light mode (removes dark class) |
| dark | Forces dark mode (adds dark class) |
| system | Follows prefers-color-scheme media query |
Properties
| Property | Type | Description |
|---|---|---|
| theme | "light" | "dark" | "system" | Get or set the current theme preference |
| resolvedTheme | "light" | "dark" | The actual theme being applied (resolves "system" to light or dark) |
const switcher = document.querySelector('st-theme-switcher')
// Get current preference
console.log(switcher.theme) // "light" | "dark" | "system"
// Get resolved theme (what's actually applied)
console.log(switcher.resolvedTheme) // "light" | "dark"
// Set theme programmatically
switcher.theme = 'dark'
Events
| Event | Detail |
|---|---|
| st-theme-switcher:change | { theme, resolvedTheme } |
document.addEventListener('st-theme-switcher:change', (e) => {
console.log('Theme changed:', e.detail.theme)
console.log('Resolved to:', e.detail.resolvedTheme)
})
Active State Styling
The component automatically sets aria-current="true" on the button matching the current theme. Use this for styling the active state:
<style>
st-theme-switcher [data-theme][aria-current="true"] {
background-color: var(--accent);
color: var(--accent-foreground);
}
</style>
<!-- Or with Tailwind -->
<button data-theme="light" class="aria-[current=true]:bg-neutral-200 dark:aria-[current=true]:bg-neutral-700">
Light
</button>
Tailwind CSS Integration
The component adds/removes the dark class on <html>, which works with Tailwind's class-based dark mode:
// tailwind.config.js
module.exports = {
darkMode: 'class',
// ...
}
Then use dark: variants throughout your markup:
<div class="bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white">
Content adapts to theme
</div>
No-JavaScript Fallback
Without JavaScript, the theme defaults to system preference via CSS media queries. Consider adding a fallback script in your <head> to prevent flash of incorrect theme:
<script>
(function() {
var theme = localStorage.getItem('stellify.theme');
if (theme === 'dark' || (theme === 'system' && matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
})();
</script>