Theme Utility

Table of Contents

Description

The theme utility provides a centralized way to manage the application's theme state. It handles theme persistence via localStorage, automatic system preference detection, and reactive updates across components. This utility works independently of the ThemeSwitcher component, allowing any part of your application to read, set, or subscribe to theme changes.

Basic Usage

Import and use the theme utility to manage application themes:

import theme from './utils/theme.js';

// Get current theme
const currentTheme = theme.get(); // 'auto', 'light', or 'dark'

// Set theme
theme.set('dark');

// Subscribe to theme changes
const unsubscribe = theme.subscribe((newTheme) => {
console.log('Theme changed to:', newTheme);
});

API Methods

theme.get()

Returns the current theme value.

Returns: string

One of: 'auto', 'light', or 'dark'

const currentTheme = theme.get();
console.log(currentTheme); // 'auto', 'light', or 'dark'

theme.set(value)

Sets the theme and automatically persists it to localStorage and updates the DOM.

value: string

The theme to set. Must be one of: 'auto', 'light', or 'dark'

theme.set('dark');

theme.subscribe(callback)

Subscribes to theme changes. The callback is immediately invoked with the current theme, then called again whenever the theme changes.

callback: Function

Function to call when the theme changes. Receives the new theme value as an argument.

Returns: Function

An unsubscribe function to remove the subscription.

const unsubscribe = theme.subscribe((newTheme) => {
console.log('Theme is now:', newTheme);
});

// Later, stop listening
unsubscribe();

theme.getCalculated()

Returns the effective theme, resolving 'auto' to either 'light' or 'dark' based on the user's system preference.

Returns: string

Either 'light' or 'dark'

const effectiveTheme = theme.getCalculated();
console.log(effectiveTheme); // Always 'light' or 'dark'

Examples

Reading Theme Without Component

import theme from './utils/theme.js';

// Get theme anywhere in your app
const currentTheme = theme.get();
const isDark = theme.getCalculated() === 'dark';

if(isDark){
// Apply dark-specific logic
}

Component Subscribing to Theme

import { LitElement, html } from 'lit';
import theme from './utils/theme.js';

class MyComponent extends LitElement {
static properties = {
theme: { type: String }
};

connectedCallback() {
super.connectedCallback();
this.unsubscribe = theme.subscribe((t) => {
this.theme = t;
});
}

disconnectedCallback() {
super.disconnectedCallback();
if(this.unsubscribe) this.unsubscribe();
}

render() {
return html`<p>Current theme: ${this.theme}</p>`;
}
}

Setting Theme Programmatically

import theme from './utils/theme.js';

// Set theme based on user preference from API
const userPreferences = await fetchUserPreferences();
theme.set(userPreferences.theme);

// Toggle between light and dark
const toggleTheme = () => {
const current = theme.get();
if(current === 'dark') theme.set('light');
else theme.set('dark');
};

Working Example

<div id="theme-demo">
  <p>Current theme: <strong id="theme-display">auto</strong></p>
  <p>Calculated: <strong id="calculated-display">dark</strong></p>
  <button id="set-auto">Auto</button>
  <button id="set-light">Light</button>
  <button id="set-dark">Dark</button>
</div>

<script type="module">
import theme from '../src/utils/theme.js';

// Subscribe to updates
theme.subscribe((t) => {
  document.getElementById('theme-display').textContent = t;
  document.getElementById('calculated-display').textContent = 
    theme.getCalculated();
});

// Button handlers
document.getElementById('set-auto')
  .addEventListener('click', () => theme.set('auto'));
document.getElementById('set-light')
  .addEventListener('click', () => theme.set('light'));
document.getElementById('set-dark')
  .addEventListener('click', () => theme.set('dark'));
</script>

Current theme: auto

Calculated: dark

Automatic Features

localStorage Persistence

The theme utility automatically saves the current theme to localStorage with the key 'theme'. This means the user's theme preference persists across page reloads and browser sessions.

DOM Attribute Updates & Kempo CSS Integration

Whenever the theme changes, it's automatically applied to document.documentElement as a theme attribute. This sets the color-scheme property which enables Kempo CSS's light-dark() function to work correctly.

Kempo CSS provides theme-aware CSS custom properties that automatically adapt to light and dark modes. See the Kempo CSS documentation for details on available color variables and theming.

System Preference Detection

When the theme is set to 'auto', the utility monitors the system's prefers-color-scheme media query and updates the auto-theme attribute on the document element. The getCalculated() method resolves the actual theme based on this preference.