// stores.ts
import{writable,typeWritable}from'svelte/store';// Possible theme options
exporttypeThemeType='light'|'dark'|'auto';// Preference store: any other preference can be added here
exporttypePrefType={theme: ThemeType;}// Store type: this is the object that will be stored in localstorage
exporttypeStoreType={pref: PrefType;}// Initialize the store
conststored=localStorage.content;conststore: Writable<StoreType>=writable(stored?JSON.parse(stored):{pref:{theme:'auto',}});store.subscribe(value=>{localStorage.content=JSON.stringify(value);});exportdefaultstore;
Svelte component
Finally, create a Svelte component to let user select the color theme.
The gist is to use prefers-color-schememedia query to detect the user’s system preference. If the user selects a specific theme from dropdown, it will override the system preference.
Info
For the dropdown component, I created my own with i18n support. The code is available here, but any dropdown component will work.
<!-- Preference.svelte --><scriptlang="ts">importCardfrom"$lib/Card.svelte";import{onDestroy}from"svelte";importstore,{typeThemeType}from"./stores";importDropdownfrom"./Dropdown.svelte";let{onConfirm=()=>{}}:{onConfirm?:()=>void;}=$props();constswitchColor=(dark: boolean)=>{if(dark){document.documentElement.dataset.scheme='dark';}else{document.documentElement.dataset.scheme='light';}};constmediaQuery=window.matchMedia('(prefers-color-scheme: dark)');letswitchFn:((e: MediaQueryListEvent)=>void)|null=null;constswitchTheme=(theme: ThemeType)=>{if(theme==='auto'){// Add the listener only if it doesn't exist
if(!switchFn){switchFn=(e: MediaQueryListEvent)=>switchColor(e.matches);mediaQuery.addEventListener('change',switchFn);}// Set the initial color based on current preference
switchColor(mediaQuery.matches);return;}// Remove the listener if switching to a specific theme
if(switchFn){mediaQuery.removeEventListener('change',switchFn);switchFn=null;}// Manually set the theme
switchColor(theme==='dark');};// Subscribe to store changes
constunsubscribe=store.subscribe(store=>{// theme
consttheme=store.pref.theme;switchTheme(theme);});// Cleanup on component destroy
onDestroy(()=>{if(switchFn){mediaQuery.removeEventListener('change',switchFn);}unsubscribe();});// Theme options
typethemeOptType={value: ThemeType,txLabel: string};letthemeOptions: themeOptType[]=[{value:'light',txLabel:'pref.colorTheme.light'},{value:'dark',txLabel:'pref.colorTheme.dark'},{value:'auto',txLabel:'pref.colorTheme.auto'},];</script><!--...--><Dropdownbind:selected={$store.pref.theme}options={themeOptions}onChange={onConfirm}/><!--...-->
Main html file update
There are two things to do in the main html file, app.html:
add the global.css stylesheet
add a script to detect the user’s system preference
The reason to add the script in the <header> is to set the color theme before the page is rendered. This will prevent the page from flickering when first loaded, which was the problem in the original post.
<!-- app.html --><!doctype html><htmllang="en"><head><metacharset="utf-8"/><linkrel="icon"href="%sveltekit.assets%/favicon.png"/><!-- Add global css --><linkrel="stylesheet"href="%sveltekit.assets%/css/global.css"/><metaname="viewport"content="width=device-width, initial-scale=1"/> %sveltekit.head%
<!-- set the color scheme variable before page render --><script>(function(){// Check localStorage for theme
constpref=JSON.parse(localStorage.getItem('content')||'{}').pref||{};// TODO: adjust this to your store structure
consttheme=pref.theme||'auto';if(theme==='dark'||(theme==='auto'&&window.matchMedia('(prefers-color-scheme: dark)').matches)){document.documentElement.dataset.scheme='dark';}else{document.documentElement.dataset.scheme='light';}})();</script></head><bodydata-sveltekit-preload-data="hover"><divstyle="display: contents">%sveltekit.body%</div></body></html>