Creating a Theme System in Svelte Kanban Apps

Introduction to Theming in Svelte Applications

Welcome to the fourth lesson of our Advanced UI Features and Theming course! In our previous lessons, we've built a comprehensive notification system to provide feedback after actions, implemented loading indicators to show when operations are in progress, and created empty states to guide users when there's no content to display. Now, we'll focus on the final piece of our UI enhancement journey: creating a theme system.

Theming is a crucial aspect of modern web applications that goes beyond mere aesthetics. A well-implemented theme system serves several important purposes:

  • Accessibility: Different users have different visual needs. Some users may find light themes easier to read, while others might prefer dark themes to reduce eye strain, especially in low-light environments.
  • User Preference: Allowing users to choose their preferred visual style creates a more personalized experience, increasing satisfaction and engagement.
  • Brand Consistency: Themes help maintain a consistent visual language across your application, reinforcing your brand identity.

Our Kanban application already handles notifications, loading states, and empty states, but it currently has a fixed visual style. By implementing a theme system, we'll allow users to switch between light, dark, and sepia modes according to their preferences.

For our theme implementation, we'll use CSS variables (also known as custom properties) as the foundation. This approach offers several advantages:

  • Dynamic Updates: CSS variables can be updated at runtime without requiring a full page reload.
  • Cascading Inheritance: Variables defined at the root level cascade down to all elements, making global theme changes simple.
  • Reduced Redundancy: We can define colors once and reuse them throughout the application.

Let's start by setting up our theme configuration and state management using Svelte's runes system.

Setting Up the Theme Configuration

The first step in creating our theme system is to define the available themes and their color palettes. We'll create a dedicated file to manage our theme state and functionality.

Create a new file called themeStore.svelte.js in the src/lib directory:

// Theme store with persistence using localStorage
// Available themes
export const themes = {
  light: {
    name: 'light',
    colors: {
      background: '#ffffff',
      text: '#2d3748',
      primary: '#4299e1',
      secondary: '#a0aec0',
      accent: '#ed8936',
      muted: '#e2e8f0',
      columnBg: '#f4f4f4',
      cardBg: '#ffffff',
      success: '#48bb78',
      error: '#f56565',
      warning: '#ed8936'
    }
  },
  dark: {
    name: 'dark',
    colors: {
      background: '#1a202c',
      text: '#e2e8f0',
      primary: '#63b3ed',
      secondary: '#718096',
      accent: '#f6ad55',
      muted: '#2d3748',
      columnBg: '#2d3748',
      cardBg: '#4a5568',
      success: '#68d391',
      error: '#fc8181',
      warning: '#f6ad55'
    }
  },
  sepia: {
    name: 'sepia',
    colors: {
      background: '#f8f0e3',
      text: '#433422',
      primary: '#aa5c3b',
      secondary: '#8c7851',
      accent: '#cb6e17',
      muted: '#e0d6c2',
      columnBg: '#e0d6c2',
      cardBg: '#f8f0e3',
      success: '#7b8f63',
      error: '#be6464',
      warning: '#cb6e17'
    }
  }
};

In this code, we define three themes: light, dark, and sepia. Each theme has a name property and a colors object containing various color values for different UI elements. These color values will be applied as CSS variables throughout our application.

The light theme uses a white background with dark text, providing high contrast for readability. The dark theme inverts this with a dark background and light text, reducing eye strain in low-light environments. The sepia theme uses warm, paper-like colors that some users find more comfortable for reading.

Now, let's add state management for the active theme using Svelte's runes:

// Initialize active theme from localStorage or default to light
let activeTheme = $state(
  typeof localStorage !== 'undefined' && localStorage.getItem('kanban-theme')
    ? localStorage.getItem('kanban-theme')
    : 'light'
);

export const getActiveTheme = () => activeTheme

/**
 * Switch the active theme and persist the setting
 * @param {string} themeName - Name of the theme to switch to
 */
export function setTheme(themeName) {
  if (themes[themeName]) {
    activeTheme = themeName;
    
    // Persist theme selection
    if (typeof localStorage !== 'undefined') {
      localStorage.setItem('kanban-theme', themeName);
    }
    
    // Apply CSS variables
    applyThemeToDOM(themes[themeName].colors);
  }
}

// Get current theme object
export const getCurrentTheme = () => themes[activeTheme] || themes.light;

Here, we use the $state rune to create a reactive variable called activeTheme. This variable is initialized from localStorage if a theme preference exists or defaults to light if no preference is found. The check for typeof localStorage !== 'undefined' ensures that our code works in environments where localStorage isn't available, such as during server-side rendering.

We also define a setTheme function that updates the active theme, persists the selection to localStorage, and applies the theme colors to the DOM. The getCurrentTheme function provides a convenient way to access the current theme object. We also create a getter getActiveTheme as in svelte reactive state cannot be directly exported.

With our theme configuration and state management in place, let's move on to implementing theme persistence and system preference detection.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal