This script uses the js-cookie package to manage cookies. The theme switcher example has additional dependencies.
bash
pnpm add js-cookie
The Store
cookieStore is a writeable store that will automatically update the cookie whenever the store is updated.
serverGetCookie is a function that should be called from the load function of a page/layout server route.
ts
import { browser } from "$app/environment";import type { Cookies } from "@sveltejs/kit";import Cookie from "js-cookie";import { writable, type Writable } from "svelte/store";export type CookieStore<T extends string | number | boolean | object> = Writable<T> & { initial: T };/** * Create a cookie store that will automatically update the cookie whenever the store is updated. * * @param cookie The name of the cookie * @param initial The initial value of the cookie * @returns The cookie store */export const cookieStore = function <T extends string | number | boolean | object>( name: string, initial: T, expires = 1000 * 60 * 60 * 24 * 365 // 1 year) { const cookie = writable(initial); cookie.subscribe((value) => { if (!browser) return; Cookie.set(name, typeof value !== "string" ? JSON.stringify(value) : value, { path: "/", expires: new Date(Date.now() + expires) }); }); return { ...cookie, initial };};/** * Get a cookie from the server. * * The function should be called from the `load` function of a page/layout server route. The cookie will be returned as part * of the `load` function's return value. * * ```ts * const defaultValue = { * descriptions: false * }; * * export const load = async (event) => { * const cookie = serverGetCookie(event.cookies, "settings", defaultValue); * return { * ...cookie // {descriptions: boolean} * }; * }; * ``` * * @param cookies Cookies object from the server * @param name Name of the cookie * @param defaultCookie Default value of the cookie * @returns The cookie value */export function serverGetCookie<T extends string | number | boolean | object>(cookies: Cookies, name: string, defaultCookie: T) { const val = cookies.get(name) === "undefined" ? undefined : cookies.get(name); const cookie = val ? (JSON.parse(val) as T) : defaultCookie; if (typeof cookie !== typeof defaultCookie) throw new Error(`Cookie "${name}" is not of type ${typeof defaultCookie}`); if (typeof cookie === "object" && typeof defaultCookie === "object") return { ...defaultCookie, ...cookie }; else return cookie;}
Usage
The initial value should be set from the cookie if it already exists. Get the cookie from the server for SSR and set the initial value to the cookie value in the layout.
svelte
<script lang="ts"> import { cookieStore } from "$server/cookie"; import { setContext } from "svelte"; export let data; // data.app is the value of the cookie, if it exists $: app = setContext("app", cookieStore("app", data.app));</script>
Example: Creating a Theme Switcher with DaisyUI
Dependencies
The theme switcher uses the @sveltekit-addons/document package to change the <html> tag’s data-theme and class attributes.
The @sveltekit-addons/document package provides a preprocessor that allows you to modify the <html> and <body> tags from any page or layout. For setup instructions, see the documentation.
bash
pnpm add -D @sveltekit-addons/document
Setup
The App.Cookie type is used to define the cookie structure in the implementation. app.settings.theme is of type Themes, which is a union of the available theme names, and app.settings.mode is of type ThemeGroups, which is a union of "dark" | "light".
ts
import type { ThemeGroups, Themes } from "$lib/constants";declare global { namespace App { interface Cookie { settings: { theme: Themes; mode: ThemeGroups; }; } }}
The themes in constants.ts correspond to the themes configured in the DaisyUI plugin. The ThemeSwitcher component will allow the user to select a theme from the available themes. If the theme is set to "system", the theme and mode will be set based on the user’s system preference and update accordingly. Otherwise, mode will be set based on the theme’s group.
svelte
<script lang="ts"> import { cookieStore } from "$server/cookie"; import { setContext } from "svelte"; export let data; $: app = setContext("app", cookieStore("app", data.app));</script><!-- In Tailwind, you can enable the `dark:` variant by setting the root class to "dark" --><!-- DaisyUI uses the data-theme attribtue to enable a specific theme --><ska:html data-theme={$app.settings.theme} class={$app.settings.mode} />
ts
import { serverGetCookie } from "$server/cookie.js";export const load = async (event) => { // Get cookie if it exists, otherwise use default settings const app = serverGetCookie<App.Cookie>(event.cookies, "app", { settings: { theme: "system", mode: "dark" } }); return { app };};
svelte
<script lang="ts"> import { themeGroups, themes } from "$lib/constants"; import type { CookieStore } from "$server/cookie"; import { browser } from "@svelteuidev/composables"; import { getContext } from "svelte"; const app = getContext<CookieStore<App.Cookie>>("app"); function watchMedia(node: HTMLElement) { const mql = window.matchMedia("(prefers-color-scheme: dark)"); mql.addEventListener("change", (ev) => { if ($app.settings.theme == "system") $app.settings.mode = ev.matches ? "dark" : "light"; }); } $: if (browser) { const selected = themes.find((t) => t.value === $app.settings.theme); if (selected) { if (selected.value === "system") { const mql = window.matchMedia("(prefers-color-scheme: dark)"); $app.settings.mode = mql.matches ? "dark" : "light"; } else { $app.settings.mode = selected.group; } } }</script><select class="select select-bordered select-sm" bind:value={$app.settings.theme} use:watchMedia> <option value="system">System</option> {#each themeGroups as group} <hr /> {#each themes.filter((t) => "group" in t && t.group === group) as theme} <option value={theme.value}>{theme.name}</option> {/each} {/each}</select>