A comprehensive light/dark mode theme system built with PAN components that automatically respects system preferences.
The PAN theme system provides:
prefers-color-schemeAdd the theme stylesheet to your HTML:
<link rel="stylesheet" href="assets/theme.css">
For files in subdirectories, adjust the path:
<link rel="stylesheet" href="../assets/theme.css">
Add the theme provider component before closing </body>:
<!-- Theme System -->
<pan-theme-provider theme="auto"></pan-theme-provider>
<script type="module">
import './components/pan-theme-provider.mjs';
import './components/pan-theme-toggle.mjs';
</script>
Add a theme toggle button to your navigation:
<pan-theme-toggle variant="icon"></pan-theme-toggle>
<pan-theme-provider>Manages theme state and broadcasts changes via PAN.
Attributes:
theme: 'light' |
'dark' |
'auto' (default: 'auto') |
PAN Events:
theme.changed: Emitted when theme changes
{ theme: 'auto', effective: 'dark' }
theme.system-changed: Emitted when system preference changes
{ theme: 'dark' }
API:
const provider = document.querySelector('pan-theme-provider');
// Set theme
provider.setTheme('dark');
// Get current theme setting
provider.getTheme(); // 'auto', 'light', or 'dark'
// Get effective theme (resolves 'auto')
provider.getEffectiveTheme(); // 'light' or 'dark'
// Get system preference
provider.getSystemTheme(); // 'light' or 'dark'
<pan-theme-toggle>Interactive theme switcher button.
Attributes:
variant: 'icon' |
'button' |
'dropdown' (default: 'icon') |
label: Optional label text (only for button variant)Variants:
<pan-theme-toggle variant="icon"></pan-theme-toggle>
<pan-theme-toggle variant="button" label="Theme"></pan-theme-toggle>
<pan-theme-toggle variant="dropdown"></pan-theme-toggle>
| Variable | Light Mode | Dark Mode |
|---|---|---|
--color-primary |
#006699 | #0099dd |
--color-primary-dark |
#004d73 | #0077bb |
--color-primary-light |
#0088cc | #33aaee |
--color-primary-soft |
#cce6f5 | rgba(0, 153, 221, 0.15) |
| Variable | Purpose |
|---|---|
--color-success |
Success states, positive actions |
--color-warning |
Warnings, caution states |
--color-danger |
Errors, destructive actions |
--color-info |
Informational messages |
| Variable | Usage |
|---|---|
--color-bg |
Page background |
--color-bg-alt |
Alternate background (sections) |
--color-surface |
Card/panel backgrounds |
--color-surface-alt |
Alternate surface color |
| Variable | Usage |
|---|---|
--color-text |
Primary text |
--color-text-muted |
Secondary/muted text |
--color-text-subtle |
Subtle/placeholder text |
| Variable | Usage |
|---|---|
--color-border |
Default borders |
--color-border-strong |
Emphasized borders |
.my-component {
background: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
}
.button-primary {
background: var(--color-primary);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
transition: background 0.2s ease;
}
.button-primary:hover {
background: var(--color-primary-dark);
}
.badge-success {
background: var(--color-success-light);
color: var(--color-success);
padding: 0.25rem 0.75rem;
border-radius: 1rem;
}
For web components with shadow DOM, inherit theme variables:
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.container {
background: var(--color-surface, #ffffff);
color: var(--color-text, #1e293b);
border: 1px solid var(--color-border, #e2e8f0);
}
</style>
<div class="container">
<slot></slot>
</div>
`;
Note: Always provide fallback colors for shadow DOM components.
Listen to theme changes in your components:
const bus = document.querySelector('pan-bus');
if (bus) {
bus.subscribe('theme.changed', (data) => {
console.log('Theme changed to:', data.effective);
// Update component state
});
}
Or use the theme provider directly:
const provider = document.querySelector('pan-theme-provider');
provider.addEventListener('theme-change', (e) => {
const { theme, effective } = e.detail;
console.log(`Theme: ${theme}, Effective: ${effective}`);
});
```
background: var(–color-surface);
color: var(–color-text);
<pan-theme-provider theme="auto"></pan-theme-provider>
<script type="module">
import './components/pan-theme-provider.mjs';
import './components/pan-theme-toggle.mjs';
</script>
Use the provided script to update multiple files:
node scripts/add-theme-support.mjs
This script:
You can override theme colors by defining variables after importing theme.css:
:root {
--color-primary: #0077cc;
--color-primary-dark: #005599;
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: #00aaff;
}
}
.my-card {
--card-bg: var(--color-surface);
--card-border: var(--color-border);
--card-text-color: var(--color-text);
}
Requires support for:
prefers-color-scheme media queryauto mode<link rel="stylesheet" href="assets/theme.css"><pan-theme-provider>import './components/pan-theme-provider.mjs'var(--color-primary)See theme-demo.html for a comprehensive demonstration of all theme colors and components.
For issues or questions about the theme system, please open an issue on GitHub.