A minimal SvelteKit + TypeScript starter with a cookie-based theme system, accent colors, and a small set of UI primitives.
system, updates live without a reloadbun install
bun dev
src/
├── app.html # HTML shell
├── hooks.server.ts # Reads theme/accent cookies, injects data-theme + data-accent on <html>
├── lib/
│ ├── components/
│ │ ├── ui/ # Button, Slider, Switch
│ │ ├── Header.svelte
│ │ └── Footer.svelte
│ └── utils/
│ ├── theme.svelte.ts # Reactive theme store (current, accent, resolved)
│ └── constants.ts # ACCENT_OPTIONS, THEME_OPTIONS, defaults
├── routes/
│ ├── +layout.server.ts # Passes theme + accent from cookies to layout
│ ├── +layout.svelte # Initializes theme store, handles system reactivity
│ └── +page.svelte # Demo page
└── styles/
├── app.css
├── theme.css # CSS vars driven by data-theme / data-accent
└── typography.css
hooks.server.ts reads theme and accent cookies on every request and injects data-theme and data-accent directly onto <html> via transformPageChunk — so the correct theme is in the DOM before the first paint, with no flash.
+layout.server.ts passes the cookie values to the layout as data.theme and data.accent.
+layout.svelte calls theme.init() synchronously (not in onMount) so the reactive store matches the server state from the very first render. It also uses Svelte's MediaQuery from svelte/reactivity to watch the OS color scheme and update the DOM in real time when the theme is set to system.
theme.svelte.ts exposes a singleton store with value (light/dark/system), active (resolved light/dark), accent, setTheme, setAccent, and toggle.
Theme and accent are stored as cookies and applied as data attributes on <html>:
data-theme="light | dark"
data-accent="gray | blue | green | ..."
CSS variables in theme.css and app.css are scoped to these attributes, so switching is instant with no JS class juggling.