theme-picker

Theme selection component for color mode (light/dark/auto) and brand themes.

Overview

The <theme-picker> component provides a UI for users to select their preferred color mode and brand theme. It integrates with ThemeManager for persistence and system preference detection.

<theme-picker> <button data-trigger> <icon-wc name="palette"></icon-wc> Theme </button> </theme-picker>

Inline Variant

For settings pages, use the inline variant which is always visible:

<theme-picker data-variant="inline"></theme-picker>

Trigger Variants

Text Button

Simple text trigger:

<theme-picker> <button data-trigger>Theme</button> </theme-picker>

Icon-Only Button

For compact layouts, use an icon-only trigger with an accessible label:

<theme-picker> <button data-trigger aria-label="Theme settings"> <icon-wc name="palette"></icon-wc> </button> </theme-picker>

Custom Styling

Apply button variant classes to match your design:

<theme-picker> <button data-trigger class="ghost"> <icon-wc name="palette" size="sm"></icon-wc> Theme </button> </theme-picker>

Attributes

Attribute Values Default Description
data-variant popover, inline popover Display mode - popover with trigger or always visible
data-open boolean - Reflected attribute indicating open state (popover only)

ThemeManager API

For programmatic theme control, use the ThemeManager module:

import { ThemeManager } from './lib/theme-manager.js'; // Initialize (called automatically in main.js) ThemeManager.init(); // Get current state const { mode, brand, effectiveMode } = ThemeManager.getState(); // Set color mode: 'auto', 'light', or 'dark' ThemeManager.setMode('dark'); // Set brand theme: 'default', 'ocean', 'forest', 'sunset' ThemeManager.setBrand('ocean'); // Toggle between light and dark ThemeManager.toggleMode(); // Reset to defaults ThemeManager.reset(); // Listen for changes window.addEventListener('theme-change', (e) => { console.log(e.detail); // { mode, brand, effectiveMode } });

Available Themes

Color Modes

Mode Description
auto Follows system preference (prefers-color-scheme)
light Forces light mode
dark Forces dark mode

Brand Themes

Theme Primary Hue Description
default 260 (purple) Default brand colors
ocean 200 (teal) Cool blue-green palette
forest 145 (green) Natural earthy greens
sunset 25 (orange) Warm orange-red palette

Creating Custom Themes

Create new brand themes by copying the template:

# Copy the template cp src/tokens/themes/_brand-template.css src/tokens/themes/_brand-coral.css

Edit the hue values (0-360):

:root[data-theme="coral"], [data-theme="coral"] { --hue-primary: 15; /* coral orange */ --hue-secondary: 350; /* pink */ --hue-accent: 180; /* teal contrast */ /* Colors are auto-calculated from hues */ --color-primary: oklch(50% 0.15 var(--hue-primary)); /* ... */ }

Register in src/tokens/themes/index.css:

@import "./_brand-coral.css";

Hue Reference

Hue Range Colors
0-30Red / Orange
30-60Orange / Yellow
60-120Yellow / Green
120-180Green / Cyan
180-240Cyan / Blue
240-300Blue / Purple
300-360Purple / Red

Preventing FOUC

To prevent a flash of unstyled content, add this inline script to your HTML <head>:

<script> try { const t = JSON.parse(localStorage.getItem('vb-theme') || '{}'); if (t.mode && t.mode !== 'auto') document.documentElement.dataset.mode = t.mode; if (t.brand && t.brand !== 'default') document.documentElement.dataset.theme = t.brand; } catch {} </script>

Events

Event Detail Description
theme-picker:open - Fired when popover opens
theme-picker:close - Fired when popover closes
theme-change { mode, brand, effectiveMode } Fired on window when theme changes

Accessibility

  • Popover uses role="dialog" with aria-label
  • Radio groups use proper role="radiogroup"
  • Trigger button announces expanded state via aria-expanded
  • Escape key closes the popover
  • Focus is trapped within popover when open