Theme Toggle Fix - Complete Solution
Problem Description
The theme toggle button was not working correctly across all pages. The issues were:
Only header responded to toggle: When clicking the theme toggle button, only the header/navbar changed color, but the body content (
.content-textparagraphs) remained unchanged.Empty body class: When toggling, the body element's class attribute was being set to an empty string (
class="") instead of properly toggling betweenclass="dark-theme"and no class.Body remained dark when switching to light: When toggling from dark to light mode, the body background remained dark instead of switching to white.
Inconsistent behavior: The theme toggle worked on
index.htmlbut not on sub-pages (in/in.html,processing/processing.html,out/out.htmland their sub-pages).
Root Causes
1. CSS Variable Inheritance Issue
The .content-text p elements used color: var(--color-text-light), which relies on CSS variable inheritance. The CSS variables weren't being updated on all parent elements in the DOM hierarchy, causing the variables to resolve to dark values even when the theme was toggled.
2. Timing Issue with Stylesheet Injection
The dynamic stylesheet that overrides CSS rules was being injected too late - only when script.js loaded at the end of the body. By that time, the page had already rendered with incorrect colors.
3. ClassList.toggle() Issue
Using classList.toggle() was causing the class to be set to an empty string in some cases instead of properly adding/removing the dark-theme class.
4. Background Color Not Explicitly Reset
When switching from dark to light mode, the background colors weren't being explicitly set to white, causing them to remain dark.
Solution
1. Inline Stylesheet Injection in HTML Head
Location: All HTML files in in/, processing/, and out/ directories
Added stylesheet injection code directly in the inline <script> tag in the <head> section of each HTML file. This ensures the stylesheet is injected immediately when the page loads, before the body content renders.
// CRITICAL: Inject stylesheet immediately in head for content-text
const darkThemeStyle = document.createElement('style');
darkThemeStyle.id = 'dark-theme-override-style';
darkThemeStyle.textContent = ':root.dark-theme,html.dark-theme,body.dark-theme{--color-text:#ffffff!important;--color-text-light:#ffffff!important}body.dark-theme .content-text,html.dark-theme .content-text,body.dark-theme .content-text *,html.dark-theme .content-text *,body.dark-theme .content-text p,html.dark-theme .content-text p,body.dark-theme .section .content-text,html.dark-theme .section .content-text,body.dark-theme .section .content-text *,html.dark-theme .section .content-text *,body.dark-theme .section .content-text p,html.dark-theme .section .content-text p,body.dark-theme main .content-text,html.dark-theme main .content-text,body.dark-theme main .content-text *,html.dark-theme main .content-text *,body.dark-theme main .content-text p,html.dark-theme main .content-text p,body.dark-theme .container .content-text,html.dark-theme .container .content-text,body.dark-theme .container .content-text *,html.dark-theme .container .content-text *,body.dark-theme .container .content-text p,html.dark-theme .container .content-text p,body.dark-theme .content-grid .content-text,html.dark-theme .content-grid .content-text,body.dark-theme .content-grid .content-text *,html.dark-theme .content-grid .content-text *,body.dark-theme .content-grid .content-text p,html.dark-theme .content-grid .content-text p{color:#ffffff!important;--color-text:#ffffff!important;--color-text-light:#ffffff!important}body.dark-theme main,html.dark-theme main,body.dark-theme .section,html.dark-theme .section,body.dark-theme .container,html.dark-theme .container,body.dark-theme .content-grid,html.dark-theme .content-grid{background-color:#1a1a1a!important;color:#ffffff!important;--color-text:#ffffff!important;--color-text-light:#ffffff!important}body.dark-theme .content-grid h1,html.dark-theme .content-grid h1,body.dark-theme .content-text h1,html.dark-theme .content-text h1{color:#ffffff!important}';
if (document.head) {
document.head.appendChild(darkThemeStyle);
} else {
(function checkHead() {
if (document.head) {
document.head.appendChild(darkThemeStyle);
} else {
setTimeout(checkHead, 0);
}
})();
}
2. Explicit Class Management Instead of Toggle
Location: script.js - initThemeToggle() function
Changed from using classList.toggle() to explicitly checking the current state and adding/removing classes:
// Before (problematic):
body.classList.toggle('dark-theme');
html.classList.toggle('dark-theme');
document.documentElement.classList.toggle('dark-theme');
// After (fixed):
const currentlyDark = body.classList.contains('dark-theme');
if (currentlyDark) {
// Switching to light
body.classList.remove('dark-theme');
html.classList.remove('dark-theme');
document.documentElement.classList.remove('dark-theme');
} else {
// Switching to dark
body.classList.add('dark-theme');
html.classList.add('dark-theme');
document.documentElement.classList.add('dark-theme');
}
3. Explicit Background Color Reset
Location: script.js - removeInlineDarkThemeStyles() function
When switching to light mode, explicitly set background colors to white instead of just removing the dark background:
// CRITICAL: Remove dark background from html and body
document.documentElement.style.removeProperty('background-color');
document.documentElement.style.setProperty('background-color', '#ffffff', 'important');
document.body.style.removeProperty('background-color');
document.body.style.setProperty('background-color', '#ffffff', 'important');
// Also for main and section containers
if (main) {
main.style.setProperty('background-color', '#ffffff', 'important');
// ... remove other styles
}
if (section) {
section.style.setProperty('background-color', '#ffffff', 'important');
// ... remove other styles
}
4. Comprehensive Stylesheet with All Selectors
Location: script.js - applyInlineDarkThemeStyles() function
Created a comprehensive dynamic stylesheet that targets all possible selector combinations:
body.dark-theme .content-text phtml.dark-theme .content-text pbody.dark-theme .section .content-text pbody.dark-theme main .content-text pbody.dark-theme .container .content-text pbody.dark-theme .content-grid .content-text p- And all their wildcard variants (
*)
This ensures that no matter the DOM structure, the styles are applied correctly.
5. Immediate Application (No setTimeout)
Location: script.js - Theme toggle click handler
Changed from using setTimeout to applying styles immediately:
// Before:
setTimeout(() => {
if (isDark) {
applyInlineDarkThemeStyles();
} else {
removeInlineDarkThemeStyles();
}
}, 0);
// After:
if (isDark) {
applyInlineDarkThemeStyles();
} else {
removeInlineDarkThemeStyles();
}
Files Modified
All HTML files (96 files) in:
in/directory and subdirectoriesprocessing/directory and subdirectoriesout/directory and subdirectories
Change: Added stylesheet injection code in the inline
<script>tag in the<head>section.script.js:
- Added IIFE at the top to inject stylesheet immediately on script load
- Modified
applyInlineDarkThemeStyles()function - Modified
removeInlineDarkThemeStyles()function - Modified
initThemeToggle()function to use explicit add/remove instead of toggle - Modified click handler to apply styles immediately
styles.css:
- Removed transitions for theme switching (set to 0.05s, then removed entirely)
- Added comprehensive dark theme rules with
!importantflags
Testing
After implementing the fix, test on:
http://localhost:8000/index.htmlhttp://localhost:8000/in/in.htmlhttp://localhost:8000/processing/processing.htmlhttp://localhost:8000/out/out.html- All sub-pages in these directories
Expected behavior:
- Theme toggle should work instantly on all pages
- Body class should be
class="dark-theme"when dark, empty when light - Body background should be white (
#ffffff) in light mode, dark (#1a1a1a) in dark mode - All text content (
.content-textand children) should change color correctly - No visible transitions or delays
Key Takeaways
Timing is critical: Stylesheets must be injected as early as possible (in the head, not at the end of body).
CSS Variable inheritance: When using CSS variables, ensure they're set on all parent elements in the hierarchy.
Explicit over implicit: Use explicit
add()/remove()instead oftoggle()when class state is critical.Comprehensive selectors: When dealing with complex DOM structures, target all possible selector combinations.
Inline styles as fallback: Use inline styles with
!importantas a fallback when CSS specificity is an issue.Immediate application: Apply styles immediately rather than deferring with
setTimeoutwhen possible.
Version History
- v=28: Final working version with all fixes applied
- v=27: Added explicit background color reset
- v=26: Added background color setting in applyInlineDarkThemeStyles
- v=25: Fixed explicit class management
- v=24: Changed from toggle to explicit add/remove
- v=23: Added inline stylesheet injection in HTML head
- v=22: Added IIFE for immediate stylesheet injection
- v=21: Added comprehensive dynamic stylesheet
- v=20: Added CSS variable updates on html/body
- v=19: Extracted helper functions
- v=18: Added inline style application
- v=17: Added transition disabling
- v=16: Initial theme toggle fixes
Date
Fixed: December 2024