# Theme Persistence _Path: en/frontend/web-host/theme-persistence_ ## Table of Contents - Theme Persistence ## Content # Theme Persistence By default the Web Host resolves light/dark from `theme_mode` (the facade default) and keeps it in memory — so a user's explicit choice is lost on the next reload. Theme persistence lets that choice survive reloads by storing it in a **cookie** or in **localStorage**, and loads it as early as possible so there is no flash of the wrong theme. Persistence lives entirely in the facade. The Web Host stays storage-agnostic: it only emits a `themeChanged` event that the facade (or any embedder) uses to persist the choice. > **Opt-in.** `theme_persist` defaults to **`none`** — persistence is **off** unless a deployment > explicitly sets it to `cookie` or `localStorage`. With the default, behavior is exactly as before > (the theme always comes from `theme_mode` and is not remembered across reloads). Nothing is stored, > no cookie is written, and the generated script is a no-op until you opt in. ## Configuration Two facade parameters control it (see [Frontend Facade](/framework/facade)): | Parameter | Default | Values | Description | |-----------|---------|--------|-------------| | `theme_persist` | `none` | `none` \| `cookie` \| `localStorage` | Where the chosen mode is stored. `none` = current behavior. | | `theme_storage_key` | `@wippy-theme-mode` | string | Cookie / localStorage key. | Both are returned by the public config endpoint as `themePersist` and `themeStorageKey`, so pages served outside the Web Host can read them too. ```yaml # in your facade dependency parameters - name: theme_persist value: cookie - name: theme_storage_key value: "@wippy-theme-mode" ``` ### cookie vs localStorage - **`cookie`** — the Jet-rendered host shell reads the cookie **server-side** and writes the `w-theme-*` class onto `` before the response is sent, so the very first paint is already themed. **No flash.** Best default. - **`localStorage`** — the server can't read localStorage, so the stored value is applied by a synchronous inline script as early as possible. A brief flash is technically possible but minimized. ## The generated script When persistence is enabled the facade **generates and serves** a small script at: ``` GET /api/public/facade/theme-persist.js ``` The configured key and mode are baked in — there is nothing to configure on the page. Include it once, as early as possible in ``: ```html ``` On load it reads the stored value and applies the `w-theme-*` class, then exposes a small API: ```js window.wippyThemePersist = { mode, // 'none' | 'cookie' | 'localStorage' key, // the storage key read(), // -> 'auto' | 'light' | 'dark' | null write(mode), // persist a mode (no-op when mode === 'none') apply(mode), // toggle the w-theme-* class on } ``` The host shell (`index.html` / the Jet `index.jet`) already includes this script, seeds the stored value into the app, and persists changes — you don't need to touch it. The sections below are for **other** pages. ## How it fits together (host shell) 1. **First paint** — cookie mode: the server set ``. localStorage mode: the early-apply script set it. Either way the page is themed before the bundle loads. 2. **Bootstrap** — the shell seeds the persisted value into the host: `themeMode: window.wippyThemePersist.read() ?? cfg.themeMode`, so the host applies the same mode. 3. **On change** — the host emits `themeChanged(mode)`; the shell persists it: `events.on('themeChanged', window.wippyThemePersist.write)`. ### The `themeChanged` host event `globalEvents` — the emitter returned by `window.initWippyApp(...)` — fires `themeChanged(mode)` (`'auto' | 'light' | 'dark'`) on init and on every theme change. It is persist-agnostic: the host never touches storage; embedders decide what to do with it. ```js const events = window.initWippyApp(config, '#app') events.on('themeChanged', (mode) => { // e.g. persist, or notify a parent window }) ``` ## Non-Wippy-hosted pages A login page, a marketing page, or any page that isn't the Web Host can honour and persist the same theme. Include the generated script and call `write()` from your own switcher: ```html ``` Because the key and storage mode are shared (the script is generated from the same facade config), a choice made on the login page carries straight into the Web Host, and vice-versa. > If you'd rather not load the script, you can fetch `/api/public/facade/config`, read > `themePersist` / `themeStorageKey`, and implement read/write yourself — but the generated script > keeps the storage logic in one place. ## Server-side cookie rendering (zero flash) For a custom server-rendered page (e.g. a Jet login template) you can apply the theme server-side, exactly like the host shell does: read the cookie named by `theme_storage_key` from the request and emit the matching class on ``: ```html ``` where the handler set `themeClass` to `w-theme-dark` / `w-theme-light` (and `colorScheme` to `dark` / `light`) based on the cookie. Still include `theme-persist.js` so the page can write changes back. ## Navigation Previous: CSS Injection (frontend/web-host/css-injection) Next: @wippy-fe Packages (frontend/web-host/packages)