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

PropTypeDefaultDescription
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);