## Demo: mode-watcher ### ModeWatcher.svelte ```svelte

mode-watcher needs a little setup: you mount its <ModeWatcher /> component once, and it reads localStorage and applies the mode to the global <html> element — adding a dark class and setting color-scheme. This site drives its own theme off a data-theme attribute, so the two coexist without collision.

Mount it once, then any button can drive the mode:

Full API at mode-watcher.dev.

Toggle & set the mode

toggleMode, setMode, and resetMode drive the global theme; the mode, userPrefersMode, and systemPrefersMode runes report the current state.

SSR-friendly theme, no flash

mode-watcher only persists to localStorage, which the server can't read — so on its own the first paint can flash. The bar above fixes that: the island mirrors the resolved mode into a cookie, serverProps reads it back into <ModeWatcher defaultMode>, and a transformPage hook stamps class="dark" onto <html> before the response is sent. Pick a mode, then reload — the page comes back correct with no flicker (server sent: shows the value the server used). "system" can't be server-resolved without JS, so the cookie stores the resolved light/dark.

``` ### ModeControls.svelte ```svelte
SSR theme server-rendered from a cookie — no flash on reload
server sent: {initialMode ?? '(no cookie yet)'}

toggleMode

flip the global <html> between light and dark
{#if mode.current} {mode.current} {:else} undefined (server) {/if}

setMode / resetMode

user pick vs resolved vs OS
userPrefersMode
{userPrefersMode.current}
mode (resolved)
{mode.current ?? '—'}
systemPrefersMode
{systemPrefersMode.current ?? '—'}
``` ### routes.ts ```ts import { Mochi, getRequestContext } from 'mochi-framework'; import type { MochiRouteValue, Handle } from 'mochi-framework'; import { THEME_COOKIE } from './constants'; export const routes: Record = { '/demos/mode-watcher': Mochi.page('./src/demos/mode-watcher/ModeWatcher.svelte', { serverProps: () => ({ initialMode: getRequestContext().cookies.get(THEME_COOKIE) ?? null }), }), }; // SSR the resolved theme: read the cookie the island mirrors the mode into, and set the `dark` // class on before it's sent so the first paint already matches — no flash on reload. export const handle: Handle = async ({ event, resolve }) => { if (!event.url.pathname.startsWith('/demos/mode-watcher')) { return resolve(event); } if (getRequestContext().cookies.get(THEME_COOKIE) !== 'dark') { return resolve(event); } return resolve(event, { transformPage: ({ html }) => html.replace('', ''), }); }; ``` ### constants.ts ```ts // Shared by routes.ts (server read) and ModeControls.svelte (client write) so the name can't drift. export const THEME_COOKIE = 'mochi-demo-theme'; ``` ### index.ts ```ts import { Mochi, logger } from 'mochi-framework'; import { routes } from './routes'; await Mochi.serve({ port: 3333, development: process.env.MODE === 'development', routes, }); logger.info('Server running at http://localhost:3333'); ```