Svelte Theme Picker
A beautiful, customizable theme picker component for Svelte applications.
Features
- 10 beautiful built-in themes (dark and light modes)
- Fully customizable - add your own themes
- Persists selection to localStorage
- Applies CSS variables to your document
- Svelte 5 runes compatible
- Full TypeScript support
- Floating button or inline mode (vertical/horizontal)
- Preview mode - temporarily apply themes without persisting
- JSON-driven themes - define themes in JSON with production/test filtering
- Theme catalogs - organize with metadata, tags, and filtering
- Headless mode - use the store without UI for programmatic control
- SSR support - prevent theme flash with ThemeHead component
- Cross-tab sync - automatically sync themes across browser tabs
Current Theme
Click the theme button in the bottom-right corner to select a theme.
Theme Colors
Primary Colors
Accent Colors
Text Hierarchy
Primary text color
Secondary text color
Muted text color
Implementation
Drop in a floating theme picker button with zero configuration. Appears in the bottom-right corner by default.
Basic Usage
<script>
import { ThemePicker } from 'svelte-theme-picker';
</script>
<ThemePicker />Customize the storage key, default theme, button position, and get notified when themes change.
With Configuration
<script lang="ts">
import {
ThemePicker,
type Theme,
type ThemePickerConfig
} from 'svelte-theme-picker';
const config: ThemePickerConfig = {
storageKey: 'my-app-theme',
defaultTheme: 'ocean',
};
function handleChange(id: string, theme: Theme) {
console.log('Theme changed:', theme.name);
}
</script>
<ThemePicker
{config}
position="bottom-left"
onThemeChange={handleChange}
/>Control themes via the store API. Useful for custom UIs, keyboard shortcuts, or syncing with external state.
Programmatic Control
<script lang="ts">
import {
themeStore,
applyTheme,
defaultThemes
} from 'svelte-theme-picker';
// Change theme programmatically
themeStore.setTheme('cyberpunk');
// Subscribe to theme changes
themeStore.subscribe((themeId) => {
const theme = defaultThemes[themeId];
console.log('Now using:', theme?.name);
});
// Apply a theme directly
applyTheme(defaultThemes.sunset);
</script>The ThemePicker component is optional. Use just the store and utilities for full control without any UI.
Headless Mode (No UI)
<script lang="ts">
// No ThemePicker component needed!
import {
themeStore,
applyTheme,
defaultThemes
} from 'svelte-theme-picker';
import { onMount } from 'svelte';
onMount(() => {
// Load theme from user preferences, database, etc.
const userTheme = getUserPreference() || 'dreamy';
themeStore.setTheme(userTheme);
applyTheme(defaultThemes[userTheme]);
// Subscribe to persist changes
return themeStore.subscribe((themeId) => {
saveUserPreference(themeId);
});
});
</script>
<!-- Your app content - no picker UI rendered -->
<slot />Define your own brand themes with full control over colors, fonts, and visual effects.
Custom Themes
<script lang="ts">
import {
ThemePicker,
type Theme
} from 'svelte-theme-picker';
const myThemes: Record<string, Theme> = {
'brand-dark': {
name: 'Brand Dark',
description: 'Our brand colors',
colors: {
bgDeep: '#0f172a',
bgMid: '#1e293b',
bgCard: '#334155',
bgGlow: '#475569',
bgOverlay: '#1e293b',
primary1: '#38bdf8',
primary2: '#0ea5e9',
primary3: '#06b6d4',
primary4: '#22d3ee',
primary5: '#67e8f9',
primary6: '#a5f3fc',
accent1: '#f472b6',
accent2: '#ec4899',
accent3: '#db2777',
textPrimary: '#f8fafc',
textSecondary: '#cbd5e1',
textMuted: '#64748b',
},
fonts: {
heading: "'Inter', sans-serif",
body: "'Inter', sans-serif",
mono: "'Fira Code', monospace",
},
effects: {
glowColor: 'rgba(56, 189, 248, 0.2)',
glowIntensity: 0.3,
particleColors: ['#38bdf8', '#f472b6'],
useNoise: false,
noiseOpacity: 0,
},
},
};
</script>
<ThemePicker
config={{
themes: myThemes,
defaultTheme: 'brand-dark'
}}
/>Store themes in JSON files with metadata for filtering. Keep test and seasonal themes documented but hidden from production.
JSON-Driven Themes
// themes.json - Define ALL themes (production + test)
{
"brand-dark": {
"theme": { /* full theme */ },
"meta": { "active": true, "tags": ["dark", "brand"] }
},
"christmas": {
"theme": { /* seasonal theme */ },
"meta": { "active": false, "tags": ["seasonal"] }
}
}
// Load and filter for production
import { loadCatalogFromJSON, getActiveThemes } from 'svelte-theme-picker';
import themesJson from './themes.json';
const catalog = loadCatalogFromJSON(themesJson);
const productionThemes = getActiveThemes(catalog);
<ThemePicker config={{ themes: productionThemes }} />Temporarily apply themes without persisting to localStorage. Ideal for settings forms where users can preview before saving.
Preview Mode (Forms)
<script>
import { themeStore, applyTheme, defaultThemes } from 'svelte-theme-picker';
import { onMount } from 'svelte';
let selectedTheme = 'dreamy';
onMount(() => {
// Revert if user leaves without saving
return () => themeStore.revertPreview();
});
function previewTheme(id) {
selectedTheme = id;
themeStore.previewTheme(id); // Temporary
applyTheme(defaultThemes[id]);
}
function saveTheme() {
themeStore.setTheme(selectedTheme); // Persist
}
</script>Prevent theme flash during SSR hydration. The ThemeHead component injects a blocking script that applies CSS variables before first paint.
SSR Support (v1.1.0+)
<!-- src/routes/+layout.svelte -->
<script>
import { ThemeHead, ThemePicker, defaultThemes } from 'svelte-theme-picker';
import { onMount } from 'svelte';
// Remove no-transitions class after hydration
onMount(() => {
setTimeout(() => {
document.documentElement.classList.remove('no-transitions');
}, 50);
});
</script>
<!-- Prevents theme flash on page load -->
<ThemeHead
themes={defaultThemes}
storageKey="my-app-theme"
defaultTheme="dreamy"
preloadFonts={true}
/>
<ThemePicker config={{ storageKey: 'my-app-theme' }} />
<slot />Automatically sync theme changes across browser tabs. When a user changes the theme in one tab, all other tabs update instantly.
Cross-Tab Sync (v1.1.0+)
<script>
import { createThemeStore, ThemePicker } from 'svelte-theme-picker';
import { onDestroy } from 'svelte';
// Create store with cross-tab sync enabled
const store = createThemeStore({
syncTabs: true, // Theme changes sync across tabs
storageKey: 'my-app-theme',
});
// Clean up listener when component unmounts
onDestroy(() => {
store.destroy();
});
</script>
<ThemePicker {store} />These CSS custom properties are automatically set on your document root when a theme is applied.
CSS Variables Applied
/* Backgrounds */
--bg-deep, --bg-mid, --bg-card, --bg-glow, --bg-overlay
/* Primary palette (1-6) */
--primary-1, --primary-2, --primary-3,
--primary-4, --primary-5, --primary-6
/* Accent colors (1-3) */
--accent-1, --accent-2, --accent-3
/* Text colors */
--text-primary, --text-secondary, --text-muted
/* Fonts */
--font-heading, --font-body, --font-mono
/* Effects */
--shadow-glow, --glow-color, --glow-intensityTypeScript Types
All types are exported for full TypeScript support. Import what you need for type-safe theme definitions.
Main interfaces for theme definitions and picker configuration.
Core Types
interface Theme {
name: string;
description: string;
colors: ThemeColors;
fonts: ThemeFonts;
effects: ThemeEffects;
mode?: 'light' | 'dark'; // v1.1.0+
}
interface ThemePickerConfig {
storageKey?: string;
defaultTheme?: string;
themes?: Record<string, Theme>;
cssVarPrefix?: string;
syncTabs?: boolean; // v1.1.0+ Cross-tab sync
}The theme store extends Svelte's Writable store with theme-specific methods including preview mode.
Store Interface
interface ThemeStore extends Writable<string> {
setTheme: (themeId: string) => void;
getTheme: (themeId: string) => Theme | undefined;
getAllThemes: () => Record<string, Theme>;
getCurrentThemeId: () => string;
// Preview mode methods
previewTheme: (themeId: string) => void;
revertPreview: () => void;
isPreviewMode: () => boolean;
getPersistedThemeId: () => string;
// Cleanup (v1.1.0+)
destroy: () => void;
}Types for organizing themes with metadata, tags, and filtering options.
Catalog Types (for JSON-driven themes)
interface ThemeMeta {
active?: boolean; // Show in production?
tags?: string[]; // e.g. ['dark', 'brand']
order?: number; // Sort order
seasonal?: string | boolean; // 'winter', 'spring', etc.
addedDate?: string; // ISO date string
}
interface ThemeCatalogEntry {
theme: Theme;
meta?: ThemeMeta;
}
type ThemeCatalog = Record<string, ThemeCatalogEntry>;
interface ThemeFilterOptions {
activeOnly?: boolean; // Only active: true
tags?: string[]; // Must have ALL tags
anyTags?: string[]; // Must have ANY tag
excludeTags?: string[]; // Exclude these tags
}