@renge-ui/svelte
Stores for reactive theming.
Svelte 4+ stores for Renge theme management. Automatic two-way binding, reactive updates, zero boilerplate. Full token integration with CSS custom properties.
Overview
What is @renge-ui/svelte?
A lightweight Svelte-first adapter for Renge tokens.
@renge-ui/svelte provides Svelte stores and utilities to consume Renge tokens with automatic reactivity. It integrates seamlessly with Svelte's built-in store mechanism and requires zero configuration beyond importing tokens.
Key features: Writable stores for profile and mode, automatic DOM synchronization, export of all Renge tokens and constants (PHI, FIBONACCI, GOLDEN_ANGLE), optional SSR support via hydration.
Getting Started
Setup in one step.
Import tokens and use Renge stores. Svelte's reactivity handles the rest.
1. Install Packages
Add both @renge-ui/svelte and @renge-ui/tokens to your project.
pnpm add @renge-ui/svelte @renge-ui/tokens2. Import Tokens
Import CSS tokens in your root layout or +page.svelte. This applies CSS custom properties globally.
<script>
import '@renge-ui/tokens/renge.css';
</script>
<slot />3. Use Stores
Import reactive stores in any component. The $ prefix auto-subscribes to changes.
<script>
import { profile, mode, switchProfile, switchMode } from '@renge-ui/svelte';
</script>
<div style="background: var(--renge-color-bg); color: var(--renge-color-fg);">
<p>Profile: {$profile}</p>
<p>Mode: {$mode}</p>
<button on:click={() => switchProfile('earth')}>
Switch to Earth
</button>
<button on:click={() => switchMode($mode === 'dark' ? 'light' : 'dark')}>
Toggle Mode
</button>
</div>Stores
Reactive stores
All stores are writable — update them directly or use helper functions.
profile
Writable store for the current color profile. Valid values: ocean | earth | twilight | fire | void | leaf.
<script>
import { profile } from '@renge-ui/svelte';
// Read current profile via store subscription
{$profile}
// Set directly
profile.set('earth');
// Subscribe manually
const unsubscribe = profile.subscribe(value => {
console.log('Profile changed to:', value);
});
</script>mode
Writable store for light/dark mode. Valid values: light | dark.
<script>
import { mode } from '@renge-ui/svelte';
// Read current mode
{$mode === 'dark' ? '🌙' : '☀️'}
// Update
mode.set('dark');
// Or toggle
mode.update(m => m === 'dark' ? 'light' : 'dark');
</script>initializeTheme()
Call during app startup to sync stores with current DOM state (data-profile and data-mode attributes).
<script>
import { onMount } from 'svelte';
import { initializeTheme } from '@renge-ui/svelte';
onMount(() => {
// Sync stores with DOM state
initializeTheme();
});
</script>🔜 Coming Soon: scale
Runtime base unit scaling (0.5–16 range). Requires calc-based token regeneration to maintain mathematical integrity.
Functions
Helper functions
Update stores and synchronize DOM. These functions handle both store updates and data attribute synchronization.
switchProfile(name: string)
Set the color profile and update the document root's data-profile attribute.
<script>
import { switchProfile } from '@renge-ui/svelte';
const handleProfileChange = (newProfile) => {
// Updates store AND DOM
switchProfile(newProfile);
};
</script>
<button on:click={() => switchProfile('twilight')}>
Switch to Twilight
</button>switchMode(mode: 'light' | 'dark')
Set light/dark mode and update the document root's data-mode attribute.
<script>
import { mode, switchMode } from '@renge-ui/svelte';
</script>
<button on:click={() => switchMode($mode === 'dark' ? 'light' : 'dark')}>
{$mode === 'dark' ? 'Light Mode' : 'Dark Mode'}
</button>Tokens
Exported tokens
Direct access to Renge tokens for programmatic use.
rengeVars
Complete token object with type definitions. Includes colors, spacing, typography, radius, duration, easing, and animation tokens.
<script>
import { rengeVars } from '@renge-ui/svelte';
// Access any token programmatically
const spacing = rengeVars.space['4']; // "16px"
const color = rengeVars.color.fg; // "oklch(...)"
</script>PHI, FIBONACCI, GOLDEN_ANGLE
Mathematical constants used in the design system.
<script>
import { PHI, FIBONACCI, GOLDEN_ANGLE } from '@renge-ui/svelte';
// PHI = 1.618033988749895
// FIBONACCI = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...]
// GOLDEN_ANGLE = 137.50776...
</script>Patterns
Common patterns
Real-world examples of theme switching in Svelte apps.
localStorage contract: switchProfile and switchMode update the Svelte store and apply CSS to the DOM immediately. They do not automatically persist to localStorage or read from it on startup. You are responsible for reading stored values in onMount and writing on change. The examples below show the full pattern.
Persist Theme
Auto-save theme changes to localStorage and restore on mount.
<script>
import { onMount } from 'svelte';
import { profile, mode } from '@renge-ui/svelte';
onMount(() => {
// Restore from localStorage
const savedProfile = localStorage.getItem('renge-profile');
const savedMode = localStorage.getItem('renge-mode');
if (savedProfile) profile.set(savedProfile);
if (savedMode) mode.set(savedMode);
});
// Auto-save on change
$: localStorage.setItem('renge-profile', $profile);
$: localStorage.setItem('renge-mode', $mode);
</script>System Preference
Detect and respect system dark mode preference.
<script>
import { onMount } from 'svelte';
import { switchMode } from '@renge-ui/svelte';
onMount(() => {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
switchMode('dark');
}
});
</script>Profile Switcher Component
Complete theme switcher with all profiles.
<script>
import { profile, switchProfile } from '@renge-ui/svelte';
const profiles = ['ocean', 'earth', 'twilight', 'fire', 'void', 'leaf'];
</script>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
{#each profiles as p (p)}
<button
class:active={$profile === p}
on:click={() => switchProfile(p)}
>
{p.charAt(0).toUpperCase() + p.slice(1)}
</button>
{/each}
</div>
<style>
button {
padding: var(--renge-space-2) var(--renge-space-3);
border: 1px solid var(--renge-color-border);
border-radius: var(--renge-radius-2);
background: var(--renge-color-bg-subtle);
color: var(--renge-color-fg);
cursor: pointer;
transition: all var(--renge-duration-2) var(--renge-easing-ease-out);
}
button:hover {
border-color: var(--renge-color-accent);
}
button.active {
background: var(--renge-color-accent);
color: var(--renge-color-bg);
border-color: var(--renge-color-accent);
}
</style>Subscription-based Updates
Use store subscriptions for side effects without reactive statements.
<script>
import { onMount } from 'svelte';
import { profile, mode } from '@renge-ui/svelte';
onMount(() => {
// Listen for profile changes
const unsubProfile = profile.subscribe(newProfile => {
console.log('Profile changed to:', newProfile);
// Send analytics, update app state, etc.
});
// Listen for mode changes
const unsubMode = mode.subscribe(newMode => {
console.log('Mode changed to:', newMode);
});
// Cleanup on unmount
return () => {
unsubProfile();
unsubMode();
};
});
</script>Integration
Framework patterns
Integrate Renge theme management with SvelteKit and other frameworks.
SvelteKit +layout.svelte
Root layout with theme persistence and initialization.
<script>
import '@renge-ui/tokens/renge.css';
import { onMount } from 'svelte';
import { initializeTheme, profile, mode } from '@renge-ui/svelte';
onMount(() => {
// Initialize from DOM/localStorage
initializeTheme();
// Restore custom theme if saved
const savedProfile = localStorage.getItem('renge-profile');
const savedMode = localStorage.getItem('renge-mode');
if (savedProfile) profile.set(savedProfile);
if (savedMode) mode.set(savedMode);
});
// Auto-persist
$: localStorage.setItem('renge-profile', $profile);
$: localStorage.setItem('renge-mode', $mode);
</script>
<slot />SSR/Hydration
Use server load to read user preferences before rendering.
// src/routes/+layout.server.ts
export async function load({ cookies }) {
return {
profile: cookies.get('renge-profile') || 'ocean',
mode: cookies.get('renge-mode') || 'light',
};
}
// src/routes/+layout.svelte
<script>
import { profile, mode, switchProfile, switchMode } from '@renge-ui/svelte';
export let data;
// Set initial values from server
$: switchProfile(data.profile);
$: switchMode(data.mode);
// Persist to cookies
$: fetch('/api/theme', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ profile: $profile, mode: $mode }),
});
</script>Shared Module
Create a reusable theme module for consistency.
// src/lib/theme.ts
import { onMount } from 'svelte';
import { profile, mode, switchProfile, switchMode } from '@renge-ui/svelte';
export function useTheme() {
onMount(() => {
const saved = {
profile: localStorage.getItem('renge-profile'),
mode: localStorage.getItem('renge-mode'),
};
if (saved.profile) switchProfile(saved.profile);
if (saved.mode) switchMode(saved.mode as 'light' | 'dark');
});
return { profile, mode, switchProfile, switchMode };
}
// Usage in components
<script>
import { useTheme } from '$lib/theme';
const { profile, switchProfile } = useTheme();
</script>Best Practices
Svelte-specific patterns
Recommended approaches for using Renge in Svelte applications.
Use reactive statements for derived state
<script>
import { profile } from '@renge-ui/svelte';
// Good: reactive statement
$: isDark = $profile === 'twilight' || $profile === 'void';
// Avoid: manual subscription
// let isDark = false;
// profile.subscribe(p => { isDark = p === 'twilight'; });
</script>Prefer CSS variables over store values in styles
<script>
import { rengeVars } from '@renge-ui/svelte';
</script>
<!-- Good: CSS variables are always in sync -->
<div style="background: var(--renge-color-bg);">Content</div>
<!-- Acceptable: use rengeVars for computed values -->
<div style="padding: {rengeVars.space['4']};">Content</div>
<!-- Avoid: store values in inline styles (stale) -->
<!-- <div style="background: {$profile};">Content</div> -->Initialize theme synchronously
<script>
import { onMount } from 'svelte';
import { profile, mode } from '@renge-ui/svelte';
// Call ASAP to prevent flash
const savedProfile = localStorage.getItem('renge-profile');
if (savedProfile) profile.set(savedProfile);
onMount(() => {
// Confirm DOM is synced
initializeTheme();
});
</script>Clean up subscriptions
<script>
import { onMount } from 'svelte';
import { profile } from '@renge-ui/svelte';
onMount(() => {
const unsub = profile.subscribe(value => {
console.log(value);
});
return unsub; // Return cleanup function
});
</script>