Skip to main content

@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.

pnpm add @renge-ui/svelte @renge-ui/tokens
v1.1.0Svelte 4+GitHub →npm →

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/tokens

2. 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>

✨ WCAG 2.1 Level AA

All color profiles meet WCAG 2.1 Level AA accessibility standards. Theme switching via Svelte stores maintains full compliance.
Scale6.0