2025-10-21 10:11:48+04:00
This commit is contained in:
162
HTML Theme Switcher Implementation.md
Normal file
162
HTML Theme Switcher Implementation.md
Normal file
@@ -0,0 +1,162 @@
|
||||
|
||||
|
||||
## What It Does
|
||||
|
||||
A three-option theme toggle (System/Light/Dark) that works reliably across all browsers.
|
||||
|
||||
**Features:**
|
||||
- Cycles through: System (💡) → Light (☀️) → Dark (🌙)
|
||||
- Persists across page refreshes
|
||||
- Follows OS theme preference when "System" is selected
|
||||
- Works in private browsing mode
|
||||
- Mobile-friendly (icon-only)
|
||||
|
||||
## The Solution
|
||||
|
||||
### CSS
|
||||
|
||||
Three separate rules that work together:
|
||||
|
||||
```css
|
||||
/* Default: Light mode */
|
||||
:root {
|
||||
--bg-primary: var(--nord-snow-storm-3);
|
||||
--text-primary: var(--nord-polar-night-2);
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/* System dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-primary: var(--nord-polar-night-1);
|
||||
--text-primary: var(--nord-snow-storm-3);
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
|
||||
/* Manual overrides (highest priority) */
|
||||
html[data-theme="dark"] {
|
||||
--bg-primary: var(--nord-polar-night-1);
|
||||
--text-primary: var(--nord-snow-storm-3);
|
||||
/* ... */
|
||||
}
|
||||
|
||||
html[data-theme="light"] {
|
||||
--bg-primary: var(--nord-snow-storm-3);
|
||||
--text-primary: var(--nord-polar-night-2);
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
**Why this works:** `html[data-theme]` has higher specificity than the media query, so manual selection always overrides system preference.
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
const THEME_OPTIONS = ['system', 'light', 'dark'];
|
||||
|
||||
function toggleTheme() {
|
||||
const current = getFromStorage('themePreference', 'system');
|
||||
const nextIndex = (THEME_OPTIONS.indexOf(current) + 1) % 3;
|
||||
const next = THEME_OPTIONS[nextIndex];
|
||||
|
||||
applyTheme(next);
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
// Update HTML attribute
|
||||
if (theme === 'system') {
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
// Update UI
|
||||
document.getElementById('themeIcon').textContent = THEME_ICONS[theme];
|
||||
document.getElementById('themeLabel').textContent = THEME_LABELS[theme];
|
||||
|
||||
// Save preference
|
||||
saveToStorage('themePreference', theme);
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
initTheme();
|
||||
```
|
||||
|
||||
### Storage Fallback
|
||||
|
||||
Works even when localStorage is blocked:
|
||||
|
||||
```javascript
|
||||
function getStorage() {
|
||||
try {
|
||||
localStorage.setItem('__test__', '1');
|
||||
localStorage.removeItem('__test__');
|
||||
return localStorage;
|
||||
} catch {
|
||||
return sessionStorage; // Fallback
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Why It Works
|
||||
|
||||
1. **System mode**: No `data-theme` attribute → media query applies
|
||||
2. **Light mode**: `data-theme="light"` → overrides media query
|
||||
3. **Dark mode**: `data-theme="dark"` → overrides media query
|
||||
|
||||
## Firefox Fix
|
||||
|
||||
**Problem:** Initial implementation had invalid CSS:
|
||||
```css
|
||||
/* ❌ Invalid */
|
||||
html[data-theme="dark"],
|
||||
@media (prefers-color-scheme: dark) { ... }
|
||||
```
|
||||
|
||||
**Solution:** Separate rules:
|
||||
```css
|
||||
/* ✅ Valid */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root { ... }
|
||||
}
|
||||
|
||||
html[data-theme="dark"] { ... }
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
12 unit tests verify:
|
||||
- Theme storage/retrieval
|
||||
- localStorage → sessionStorage fallback
|
||||
- All three theme options work
|
||||
|
||||
Run: `npm test`
|
||||
|
||||
## Available Themes
|
||||
|
||||
LinkBeam includes the following themes with automatic light/dark switching:
|
||||
|
||||
1. **auto** - Simple default theme
|
||||
2. **nord** - Nord color palette (light/dark variants)
|
||||
3. **gruvbox** - Gruvbox color palette (light/dark variants)
|
||||
4. **catppuccin-latte** - Catppuccin Latte → Mocha (light → dark)
|
||||
5. **catppuccin-frappe** - Catppuccin Latte → Frappé (light → dark)
|
||||
6. **catppuccin-macchiato** - Catppuccin Latte → Macchiato (light → dark)
|
||||
7. **catppuccin-mocha** - Catppuccin Latte → Mocha (light → dark)
|
||||
|
||||
All themes follow the same CSS structure with:
|
||||
- Default light mode colors in `:root`
|
||||
- Dark mode colors in `@media (prefers-color-scheme: dark)`
|
||||
- Manual overrides via `html[data-theme="light|dark"]`
|
||||
|
||||
## Files
|
||||
|
||||
- `templates/base.html` - Theme switcher button and script
|
||||
- `themes/*.css` - Theme CSS files with light/dark variants
|
||||
- `internal/config/config.go` - Auto-discovers available themes
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Working in all browsers
|
||||
**Last Updated:** 2025-10-12
|
||||
Reference in New Issue
Block a user