ThemeSwitcher
A SelectInput-based theme selector that allows users to choose between light, dark, and system themes. Persists to localStorage and respects OS preferences.
Installation
typescript
import { SelectInput } from '@happyvertical/smrt-svelte';
import { theme, applyTheme, type Theme } from '$lib/stores/theme';Basic Usage
Use SelectInput with the theme store for a simple theme switcher.
svelte
<script lang="ts">
import { SelectInput } from '@happyvertical/smrt-svelte';
import { theme, type Theme } from '$lib/stores/theme';
const options = [
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'system', label: 'System' }
];
function handleChange(value: string) {
theme.set(value as Theme);
}
</script>
<SelectInput
name="theme"
label="Theme"
options={options}
value={$theme}
onchange={handleChange}
/>Compact Header Variant
For use in headers, hide the label and use a smaller width.
svelte
<div style="width: 120px;">
<SelectInput
name="theme"
options={options}
value={$theme}
onchange={handleChange}
/>
</div>Theme Store Setup
Create a theme store in $lib/stores/theme.ts to manage theme state.
typescript
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export type Theme = 'light' | 'dark' | 'system';
const STORAGE_KEY = 'smrt-theme';
function getInitialTheme(): Theme {
if (!browser) return 'system';
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === 'light' || stored === 'dark' || stored === 'system') {
return stored;
}
return 'system';
}
function getSystemTheme(): 'light' | 'dark' {
if (!browser) return 'light';
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
export function getEffectiveTheme(theme: Theme): 'light' | 'dark' {
return theme === 'system' ? getSystemTheme() : theme;
}
function createThemeStore() {
const { subscribe, set, update } = writable<Theme>(getInitialTheme());
return {
subscribe,
set: (value: Theme) => {
if (browser) {
localStorage.setItem(STORAGE_KEY, value);
applyTheme(value);
}
set(value);
},
toggle: () => {
update((current) => {
const effective = getEffectiveTheme(current);
const next = effective === 'light' ? 'dark' : 'light';
if (browser) {
localStorage.setItem(STORAGE_KEY, next);
applyTheme(next);
}
return next;
});
}
};
}
export function applyTheme(theme: Theme) {
if (!browser) return;
const effective = getEffectiveTheme(theme);
document.documentElement.setAttribute('data-theme', effective);
}
export const theme = createThemeStore();Root Layout Integration
Apply the theme on mount in your root layout.
svelte
<script>
import { onMount } from 'svelte';
import { theme, applyTheme } from '$lib/stores/theme';
let { children } = $props();
onMount(() => {
applyTheme($theme);
// React to system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => {
if ($theme === 'system') applyTheme('system');
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
});
</script>
{@render children()}CSS Variables
Add dark theme CSS variables to your stylesheet. The theme is applied via the data-theme attribute on the html element.
css
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
--color-accent: #ff3e00;
--color-grid: #e5e5e5;
}
:root[data-theme='dark'] {
--color-bg: #121212;
--color-text: #e0e0e0;
--color-accent: #ff6b3d;
--color-grid: #2a2a2a;
}Store API
| Prop | Type | Default | Description |
|---|---|---|---|
theme | writable<'light' | 'dark' | 'system'> | - | The theme store. Subscribe to get current value, use .set() to change. |
applyTheme | (theme: Theme) => void | - | Apply a theme to the document. Call on mount and when theme changes. |
getEffectiveTheme | (theme: Theme) => 'light' | 'dark' | - | Resolves "system" to the actual light/dark value based on OS preference. |
TypeScript
typescript
import { theme, applyTheme, getEffectiveTheme, type Theme } from '$lib/stores/theme';
// Subscribe to theme changes
const unsubscribe = theme.subscribe((value) => {
console.log('Theme changed to:', value);
});
// Set theme programmatically
theme.set('dark');
// Toggle between light and dark
theme.toggle();
// Get the resolved theme (light or dark, never system)
const effective = getEffectiveTheme($theme);