Svelte 5
Provider and readable stores for Svelte 5, with scoped themes and transitions.
@themvora/svelteSvelte 5v2.0.0StableSSR:NoZero-flash:Partial1Install
pnpm add @themvora/core @themvora/svelte2Set up
<!-- No theme definition needed -->
<script lang="ts">
import { ThemeProvider } from "@themvora/svelte";
</script>
<!-- No `themes` prop → built-in neutral theme.
defaultTheme picks the theme; initialMode="system" is the opt-in for
following the OS. The provider hands that mode to BOTH the pre-paint
script it injects and the runtime, so the two cannot disagree. -->
<ThemeProvider defaultTheme="light" initialMode="system">
<h1>Hello, themed world</h1>
</ThemeProvider>defaultTheme chooses the starting theme. Add initialMode="system" to follow the visitor's OS on the first visit.3Add a small UI
Design system
ActiveTokens resolve at runtime, so this card follows whichever theme is active.
<div class="themvora-example-card">
<div class="themvora-example-header">
<h3 class="themvora-example-title">Design system</h3>
<span class="themvora-example-status">Active</span>
</div>
<p class="themvora-example-description">
Tokens resolve at runtime, so this card follows whichever theme is active.
</p>
<p class="themvora-example-meta">Updated 2 minutes ago</p>
<button class="themvora-example-button" type="button">View themes</button>
</div>4Style it with Themvora tokens
/* Themvora emits its design tokens as CSS custom properties on the root
element, so a stylesheet only has to read them. Nothing here is
theme-specific: these rules render every theme, including ones you add
later. */
html,
body {
background: var(--theme-color-background);
color: var(--theme-color-foreground);
}
.themvora-example-card {
display: grid;
gap: 10px;
max-width: 22rem;
padding: 16px;
border: 1px solid var(--theme-color-border);
border-radius: var(--theme-radius-lg, 12px);
background: var(--theme-color-card);
color: var(--theme-color-cardForeground);
font-family: inherit;
line-height: 1.5;
box-shadow: var(--theme-shadow-sm, 0 1px 2px rgb(0 0 0 / 0.06));
}
.themvora-example-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.themvora-example-title {
margin: 0;
font-size: 15px;
font-weight: 600;
}
.themvora-example-status {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 500;
color: var(--theme-color-success);
}
.themvora-example-status::before {
content: "";
width: 6px;
height: 6px;
border-radius: 9999px;
background: currentColor;
}
.themvora-example-description {
margin: 0;
font-size: 13px;
color: var(--theme-color-mutedForeground);
}
.themvora-example-meta {
margin: 0;
font-size: 12px;
color: var(--theme-color-mutedForeground);
}
.themvora-example-button {
justify-self: start;
padding: 8px 14px;
border: 0;
/* Presets and generated themes emit --theme-radius-lg and nothing else on
the radius scale, so the smaller step is derived from it rather than
assumed — the same proportion the utility-class mapping uses. */
border-radius: calc(var(--theme-radius-lg, 12px) * 0.8);
background: var(--theme-color-primary);
color: var(--theme-color-primaryForeground);
font: inherit;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.themvora-example-button:hover {
filter: brightness(1.08);
}
.themvora-example-button:focus-visible {
outline: 2px solid var(--theme-color-ring);
outline-offset: 2px;
}.themvora-example-card and its children read Themvora tokens, so switching the active theme repaints them — no re-render, and no per-theme CSS.The `body` rule uses exactly the two foundational colours — `background` and `foreground`. Every component rule reads one of the 21 colours `validateTheme()` requires, so any valid theme emits it. Radius and shadow are optional and carry fallbacks; no typography token is read, so a theme without a typography group still renders correctly.
5Run and choose a theme
<script lang="ts">
import { useTheme } from "@themvora/svelte";
const { theme, mode, toggleTheme } = useTheme();
</script>
<main class="page">
<h1>Themvora</h1>
<p>Current theme: {$theme.name} ({$mode})</p>
<button class="primary" onclick={toggleTheme}>Toggle theme</button>
<section class="card">
<h2>Themed card</h2>
<p class="muted">Surface, border and text follow the theme.</p>
</section>
</main>
<style>
.page {
background: var(--theme-color-background);
color: var(--theme-color-foreground);
min-height: 100vh;
padding: 2rem;
}
.primary {
background: var(--theme-color-primary);
color: var(--theme-color-primaryForeground);
border: 0;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
}
.card {
background: var(--theme-color-card);
border: 1px solid var(--theme-color-border);
border-radius: 0.75rem;
margin-top: 1.5rem;
padding: 1rem;
}
.muted {
color: var(--theme-color-mutedForeground);
}
</style>Theme configuration
<script lang="ts">
import { ThemeProvider } from "@themvora/svelte";
import ThemeSwitcher from "./ThemeSwitcher.svelte";
</script>
<!-- `defaultTheme` picks the theme. Add `initialMode="system"` to follow the
OS preference instead of the theme's own mode. -->
<ThemeProvider defaultTheme="mint-light">
<ThemeSwitcher />
</ThemeProvider>theme.config.ts and let themvoraVitePlugin() derive the pre-paint bootstrap from the same file — the provider then takes no theme props. The plugin is optional either way.What's Available
@themvora/svelte ships 17 exports in 5 categories. Click an export to see what it does and jump to the code that uses it.
Three pages cover this package, and they do different jobs: this index says what exists, the examples page shows each export in code with the problem it solves, and the API reference gives exact signatures and return types.
Use Cases
Short snippets you can drop into an existing app. For the same problems worked through end to end — problem, APIs, expectations, limitations — see the Svelte 5 examples.
Toggle light / dark
useTheme returns readable stores — prefix with $ in Svelte.
<script lang="ts">
import { useTheme } from "@themvora/svelte";
const { theme, mode, setMode, toggleTheme } = useTheme();
</script>
<button onclick={toggleTheme}>{$theme.name} · {$mode}</button>
<button onclick={() => setMode("dark")}>Dark</button>Undo / redo theme changes
History stores are reactive, just like the theme state.
<script lang="ts">
import { useThemeHistory, useThemeRuntime } from "@themvora/svelte";
const { undo, redo, canUndo, canRedo } = useThemeHistory();
const runtime = useThemeRuntime();
</script>
<button onclick={undo} disabled={!$canUndo}>Undo</button>
<button onclick={redo} disabled={!$canRedo}>Redo</button>
<button onclick={() => runtime.update({ colors: { primary: "#6366f1" } })}>
Make primary indigo
</button>Scope a subtree
ThemeScope themes a slot subtree in isolation.
<ThemeScope theme="plum-dark">
<DataViz />
</ThemeScope>Lifecycle events
Watch every theme change and install packs at runtime.
<script lang="ts">
import { onMount } from "svelte";
import { useThemeLifecycle, useThemePacks } from "@themvora/svelte";
import { getAccessibilityProfiles } from "@themvora/core";
const { on } = useThemeLifecycle();
const applyPack = useThemePacks();
onMount(() => on("beforeThemeChange", (e) => console.log(e.next.name)));
</script>
<button onclick={() => applyPack({ name: "a11y", themes: getAccessibilityProfiles() })}>
Apply High Contrast pack
</button>Smooth theme transitions
Enable CSS transitions on theme changes for a polished user experience.
<script lang="ts">
import { ThemeProvider } from "@themvora/svelte";
</script>
<ThemeProvider
transition={{ enabled: true, duration: 300, easing: "ease-in-out" }}
>
{@render children()}
</ThemeProvider>Scope and history in one component
The provider, a scoped subtree and the history stores can live in the same file: the scope takes the runtime from context rather than from a prop.
<script lang="ts">
import { ThemeProvider, ThemeScope, useThemeHistory } from "@themvora/svelte";
import ThemeSwitcher from "./ThemeSwitcher.svelte";
// `undo` / `redo` are functions; `canUndo` / `canRedo` are readable stores,
// so they are read with `$` in the markup.
const { undo, redo, canUndo, canRedo } = useThemeHistory();
</script>
<ThemeProvider defaultTheme="mint-light">
<ThemeScope theme="plum-dark" transition={{ duration: 300, easing: "ease" }}>
<p>Scoped to plum-dark</p>
</ThemeScope>
<button onclick={undo} disabled={!$canUndo}>Undo</button>
<button onclick={redo} disabled={!$canRedo}>Redo</button>
<ThemeSwitcher />
</ThemeProvider>ThemeScope around a paragraph, with undo/redo as functions and canUndo/canRedo as stores read with $.
The complete runnable app behind this guide is examples/apps/svelte — listed with the other example apps.
Related documentation
Every export in @themvora/svelte, with code and use cases
Build your own color palettes and semantic tokens
Complete @themvora/svelte documentation
Upgrade from previous versions
Check framework and library version support