This repository is the companion demo for the article "Dynamic Theme Customization at Runtime" published on The Hackpile Chronicle.
Important Note: This codebase includes production-ready enhancements beyond what's covered in the article. The article focuses on core concepts and patterns, while this repository adds security, accessibility, and performance features needed for real-world applications.
- Following the article? The core patterns work exactly as described—extra features are opt-in
- Production deployment? See PRODUCTION.md for security, accessibility & deployment guide
- What's been added? See IMPROVEMENTS.md for complete documentation
- Contrast & accessibility? See CONTRAST-USAGE.md for real-world examples
A practical Svelte 5 + SvelteKit approach to theming with a clear rule:
Beyond the article's core patterns, this repository includes enterprise-grade features:
prefers-reduced-motion and prefers-contrast support.env support)Why add these? The article demonstrates how to implement dynamic theming; this codebase shows how to do it safely and accessibly in production. Features like WCAG validation, input sanitization, and error handling are critical when user-provided colors or API data drive your UI.
See PRODUCTION.md for deployment checklist, security guidelines, and API integration
See IMPROVEMENTS.md for detailed API documentation of all enhancements
See CONTRAST-USAGE.md for contrast checking and accessible text color examples
ThemeProviderMain entry points:
src/lib/theme/theme-context.svelte.ts – theme state managementsrc/lib/theme/ThemeProvider.svelte – context provider componentsrc/lib/theme/ThemeToggle.svelte – UI toggle componentsrc/lib/theme/accessibility.svelte.ts – (NEW) a11y utilities & announcementsFive focused patterns demonstrating when runtime theming is needed:
Main entry point: src/routes/+page.svelte
Additional modules for production-ready applications:
color-utils.ts – Color math with validation, WCAG calculations, memoized functionsaccessibility.svelte.ts – Motion/contrast preferences, screen reader announcementsperformance.ts – Memoization, debounce, throttle, RAF utilitiesconfig.ts – Environment-based configuration, API integration helpersdynamic-colors.svelte.ts – Enhanced with input validation & error handlingtenant-theme.svelte.ts – Enhanced with data validation & rollback logic✅ Core APIs work exactly as described – no breaking changes!
The article teaches the fundamental patterns. You can use the code as-is:
// Article pattern - still works perfectly
import { getThemeContext } from '$lib/theme/theme-context.svelte';
const theme = getThemeContext();
theme.toggle();
All production features are opt-in additions that don't affect the core patterns.
✅ Add validation, accessibility, and performance
Enhance with production features when needed:
// Add color validation (prevents CSS injection)
import { isValidHexColor } from '$lib/theme/color-utils';
if (isValidHexColor(userColor)) {
applyDynamicColors({ accent: userColor });
}
// Check WCAG contrast (accessibility compliance)
import { meetsWCAGContrast } from '$lib/theme/color-utils';
const isAccessible = meetsWCAGContrast(foreground, background, { level: 'AA' });
// Use memoized functions (performance optimization)
import { memoized } from '$lib/theme/color-utils';
const hsl = memoized.hexToHsl(color); // Cached result
Full production guide: PRODUCTION.md Complete API reference: IMPROVEMENTS.md
Most theming guides stop at dark mode toggles. This demo covers the real-world 5% where CSS-only breaks down:
$state, $derived, $effect)lucide-svelte (icons)pnpm install
pnpm dev
Then open the local URL shown in the terminal.
pnpm build
pnpm preview
pnpm dev – run local dev serverpnpm build – create production buildpnpm preview – preview production buildpnpm check – type + Svelte checkspnpm lint – ESLint + Prettier checkspnpm format – format codesrc/
├── routes/
│ ├── +page.svelte # Main demo page (all 5 patterns)
│ └── +layout.svelte # Root layout with ThemeProvider
├── lib/
│ ├── theme/
│ │ ├── theme-context.svelte.ts # Theme state management
│ │ ├── ThemeProvider.svelte # Context provider component
│ │ ├── ThemeToggle.svelte # Theme switcher UI
│ │ ├── types.ts # TypeScript type definitions
│ │ ├── color-utils.ts # Color math, validation, WCAG (ENHANCED)
│ │ ├── dynamic-colors.svelte.ts # Dynamic color API (ENHANCED)
│ │ ├── brand-theme.svelte.ts # Pattern 2: API-driven brands
│ │ ├── color-palette.svelte.ts # Pattern 3: Palette generation
│ │ ├── chart-theme.svelte.ts # Pattern 4: Chart library integration
│ │ ├── tenant-theme.svelte.ts # Pattern 5: Multi-tenant (ENHANCED)
│ │ ├── accessibility.svelte.ts # NEW: A11y utilities
│ │ ├── performance.ts # NEW: Perf optimizations
│ │ └── config.ts # NEW: Environment config
│ └── components/
│ ├── BrandThemeDemo.svelte # Pattern 2 demo component
│ ├── ColorCustomizer.svelte # Pattern 1 demo component
│ ├── PaletteGenerator.svelte # Pattern 3 demo component
│ ├── ThemeAwareChart.svelte # Pattern 4 demo component
│ └── TenantThemeDemo.svelte # Pattern 5 demo component
├── app.css # Global styles + CSS variables
└── hooks.server.ts # SSR theme handling
.env.example # NEW: Environment variable template
PRODUCTION.md # NEW: Deployment guide
IMPROVEMENTS.md # NEW: Enhancement documentation
Key changes from article:
Read the companion article "Dynamic Theme Customization at Runtime" on The Hackpile Chronicle for in-depth explanation of the core patterns and concepts.
PRODUCTION.md – Complete production deployment guide
IMPROVEMENTS.md – Enhancement documentation
.env.example – Environment configuration template
import { getThemeContext } from '$lib/theme/theme-context.svelte';
const theme = getThemeContext();
theme.toggle(); // Switch between light/dark
theme.setPreference('dark'); // Explicit theme
import { applyDynamicColors } from '$lib/theme/dynamic-colors.svelte';
import { isValidHexColor } from '$lib/theme/color-utils';
function applyUserColor(color: string) {
if (!isValidHexColor(color)) {
console.error('Invalid color format');
return;
}
applyDynamicColors({ accent: color });
}
import { meetsWCAGContrast, findAccessibleColor } from '$lib/theme/color-utils';
function ensureAccessibleColors(fg: string, bg: string) {
if (!meetsWCAGContrast(fg, bg, { level: 'AA', size: 'normal' })) {
// Auto-fix to meet WCAG AA
fg = findAccessibleColor(fg, bg, { level: 'AA' });
}
return { foreground: fg, background: bg };
}
import { memoized } from '$lib/theme/color-utils';
import { debounce } from '$lib/theme/performance';
// Memoized color calculations
const colors = palette.map((c) => memoized.hexToHsl(c));
// Debounced user input
const debouncedApply = debounce(applyColors, 300);
This is a demo project accompanying an article. Feel free to fork and adapt for your needs!
If you find issues with the production enhancements or have suggestions, please open an issue.
MIT