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-intensity

TypeScript 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
}