PAN Theme System - Complete Solution
The Problem You Experienced
Every time you added a PAN component to a page, it didn't respect dark mode because:
--bg vs --color-bg, --primary vs --color-accent, etc.)This meant you had to manually add dark mode support to every single component and every single page. That's terrible DX!
The Solution
A centralized theme system with standardized variable names that automatically works everywhere.
How It Works
/packages/ui/theme.css) defines all color variables at the :root level@media (prefers-color-scheme) AND [data-theme] attributeWhat Was Created
#### 1. /packages/ui/theme.css
The centralized theme definition that all pages should include:
<link rel="stylesheet" href="/packages/ui/theme.css">
This file defines standard variables and responds to:
- System dark mode preference (
@media (prefers-color-scheme: dark)) - Explicit theme setting (
[data-theme="dark"]set bypan-theme-provider)
| Variable | Purpose |
|----------|---------|
| --color-bg | Main page background |
| --color-surface | Component backgrounds |
| --color-surface-alt | Alternate surfaces (headers, hover) |
| --color-border | Standard borders |
| --color-text | Primary text |
| --color-muted | Secondary text |
| --color-accent | Interactive elements (buttons, links) |
| --color-code-bg | Code block backgrounds |
#### 3. /packages/ui/scripts/fix-theme-vars.mjs
A script that automatically updates components to use standard variable names:
# Check what needs updating
node packages/ui/scripts/fix-theme-vars.mjs --dry-run
# Fix all components
node packages/ui/scripts/fix-theme-vars.mjs
# Fix specific file
node packages/ui/scripts/fix-theme-vars.mjs --file=path/to/component.mjs
#### 4. /packages/ui/THEMING.md
Complete documentation for developers on how to use the theme system.
Updated Files
- ✅
packages/ui/pan-data-table.mjs- Uses standard variables - ✅
packages/ui/pan-form.mjs- Uses standard variables - ✅
packages/ui/pan-inspector.mjs- Uses standard variables - ✅
packages/ui/pan-toast.mjs- Auto-fixed by script - ✅
examples/tutorials/assets/grail.css- Now imports from theme.css
What This Means For You
#### For Existing Pages Nothing! They automatically work now because:
grail.cssimports fromtheme.css- All components were updated to use standard variable names
- Variables inherit through shadow DOM automatically
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="/packages/ui/theme.css">
</head>
<body>
<!-- Your components will automatically respect dark mode -->
<pan-data-table resource="items"></pan-data-table>
<!-- Optional: enable theme switching -->
<pan-theme-provider></pan-theme-provider>
</body>
</html>
#### For New Components Use the standard variable names:
class MyComponent extends HTMLElement {
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
background: var(--color-surface, white);
color: var(--color-text, #333);
border: 1px solid var(--color-border, #ddd);
}
button {
background: var(--color-accent, #2563eb);
color: white;
}
</style>
<div>Content</div>
`;
}
}
That's it! Dark mode works automatically. No shadow DOM style injection needed. No per-component theme logic. No manual updates.
Why This Works
CSS custom properties (variables) are inherited properties. When you define them at :root, they inherit through shadow boundaries automatically. The key was:
:root in a global stylesheet[data-theme] attribute, all components updateThis is the standard web platform approach. We just needed to standardize the variable names and ensure all components use them consistently.
Future-Proofing
The fix-theme-vars.mjs script ensures new components follow the standard. Run it periodically:
npm run fix-theme-vars # If added to package.json scripts
Or add a pre-commit hook to check for non-standard variable names.
No More Manual Fixes!
This was the last time you need to manually add dark mode support to components. The system is now centralized and automatic. 🎉