This project uses DaisyUI's theme system for dark mode. DaisyUI provides built-in light and dark themes that are activated by setting data-theme on the <html> element. All bg-base-* and text-base-* utility classes automatically adapt.
Use this skill when users request:
- Adding dark mode to a DaisyUI/Tailwind project
- Theme toggle with localStorage persistence
- System preference detection (prefers-color-scheme)
- Smooth theme transitions
Location: web/src/js/main.js
function initTheme() {
const root = document.documentElement;
const savedTheme = localStorage.getItem('theme');
let theme;
if (savedTheme) {
theme = savedTheme;
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
theme = 'dark';
} else {
theme = 'light';
}
root.setAttribute('data-theme', theme);
// Listen for system preference changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(event) {
if (!localStorage.getItem('theme')) {
const newTheme = event.matches ? 'dark' : 'light';
root.setAttribute('data-theme', newTheme);
}
});
initThemeToggle();
}function initThemeToggle() {
// IMPORTANT: Use querySelectorAll for multiple toggle buttons (desktop + mobile)
const toggleBtns = document.querySelectorAll('[data-testid="theme-toggle"]');
if (!toggleBtns.length) return;
toggleBtns.forEach(function(toggleBtn) {
toggleBtn.addEventListener('click', function() {
const root = document.documentElement;
const currentTheme = root.getAttribute('data-theme') || 'light';
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Update ALL toggle button icons/labels
toggleBtns.forEach(function(btn) {
updateToggleLabel(btn, newTheme);
});
});
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
updateToggleLabel(toggleBtn, currentTheme);
});
}
function updateToggleLabel(button, theme) {
const isDark = theme === 'dark';
button.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode');
button.setAttribute('title', isDark ? 'Switch to light mode' : 'Switch to dark mode');
const icon = button.querySelector('.theme-icon');
if (icon) {
icon.textContent = isDark ? '☀' : '☽';
}
}Location: web/src/css/main.css
html {
transition: background-color 0.3s ease, color 0.3s ease;
}
body, .navbar, .hero, .card, .footer, section {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}- Use
querySelectorAllnotquerySelectorfor toggle buttons when there are multiple (desktop + mobile) - Remove hardcoded
data-theme="light"from<html>so JS can set it dynamically before render - Use DaisyUI semantic colors (
bg-base-100,text-base-content) rather than explicit light/dark classes - Check localStorage before system preference — user's explicit choice should override OS settings
- Update ALL toggle button labels/icons when theme changes, not just the clicked one
README.md- This documentation
web/src/js/main.js- Theme detection, toggle, and persistence logicweb/src/css/main.css- Theme transition stylesweb/templates/partials/nav.html- Theme toggle buttons in navtests/e2e/theme.spec.js- Full E2E test suite for dark mode