Skip to content
Sveltier
Mode
Getting started

Server-side rendering

Sveltier is safe to render on the server and paints the right theme from the first frame, before any JavaScript runs.

How the theme avoids a flash

  1. One stylesheet for every theme. The root provider writes a <style> into <head> with one rule per theme, keyed by data-sv-theme.
  2. "System" is pure CSS. It maps to the light or dark theme with prefers-color-scheme media rules, so no script is needed to follow the operating system.
  3. A remembered choice is restored before the body is parsed. A tiny inline script copies it from localStorage onto <html>. Only known theme names are accepted.
  4. Hydration never repaints. The provider reads the stored choice after mount, so the first client render matches the server HTML and the colours never change.

This is tested in Chromium, Firefox and WebKit by sampling every animation frame from document creation through hydration.

SSR safety

No module touches window or document at import time, and every component has a server rendering test that runs in Node.

Letting the server know the choice

The provider remembers choices in localStorage, which the server cannot read. If your server should render theme-dependent markup (for example a toggle that shows the current mode), keep the choice in a cookie and pass it as theme:

Cookie
<!-- src/routes/+layout.svelte -->
<script>
  import { UIProvider } from 'sveltier';
  import { monolith } from 'sveltier/themes/monolith';
  let { data, children } = $props();
  let theme = $state(data.theme ?? 'system');

  // Mirror the choice into a cookie so the server can render it next time.
  $effect(() => {
    document.cookie = `theme=${theme}; path=/; max-age=31536000; samesite=lax`;
  });
</script>

<UIProvider themes={monolith} bind:theme storageKey={false}>
  {@render children()}
</UIProvider>

// src/routes/+layout.server.ts
export const load = ({ cookies }) => ({ theme: cookies.get('theme') });

Content Security Policy

The provider writes one inline <style> and, when persistence is on, one inline <script>. Pass your per-request nonce, or allow the script by hash:

CSP
<UIProvider themes={monolith} nonce={data.cspNonce}>…</UIProvider>

// or allow the restore script by hash
import { themeScript, monolith } from 'sveltier';
const script = themeScript('sv-theme', ['system', ...Object.keys(monolith)]);
// sha256 of `script` goes into script-src

Set storageKey={false} to drop the script entirely.