163 lines
3.9 KiB
Markdown
163 lines
3.9 KiB
Markdown
|
|
|
|
## 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
|