UNPKG

svelte-theme-select

Version:

Customizable Svelte components for theme selection (light mode / dark mode) inspired by TailwindCSS. Flicker-free, synchronizes across tabs, works with or without SSR and doesn't require unnecessary use of `transformPageChunk` so is cache-friendly.

68 lines (67 loc) 2.08 kB
import { MediaQuery } from "svelte/reactivity"; import { on } from "svelte/events"; import { BROWSER } from "esm-env"; export const Theme = ['light', 'dark', 'system']; class ThemeState { #mq = new MediaQuery('(prefers-color-scheme: dark)'); #system = $derived(this.#mq.current ? 'dark' : 'light'); #override = $state('system'); #value = $derived(this.#override === 'system' ? this.#system : this.#override); #subscribers = 0; #off; constructor() { if (BROWSER) { const saved = localStorage.theme ?? 'system'; this.#override = saved; } } subscribe() { if ($effect.tracking()) { $effect(() => { if (this.#subscribers === 0) { this.#off = on(window, 'storage', (event) => { if (event.key === 'theme') { this.#override = event.newValue; } }); $effect(() => { document.documentElement.classList.toggle('dark', this.#value === 'dark'); }); } this.#subscribers++; return () => { this.#subscribers--; if (this.#subscribers === 0) { this.#off?.(); this.#off = undefined; } }; }); } } get system() { this.subscribe(); return this.#system; } get override() { this.subscribe(); return this.#override; } set override(value) { switch (value) { case 'dark': case 'light': localStorage.setItem('theme', value); break; case 'system': localStorage.removeItem('theme'); break; } this.#override = value; } get current() { this.subscribe(); return this.#value; } } export const theme = new ThemeState();