From 7f3f03a8065a8c30e8b6498daaf3db4295ef2035 Mon Sep 17 00:00:00 2001 From: Tirth Dhandhukia Date: Thu, 16 Oct 2025 16:08:26 +0530 Subject: [PATCH 1/2] Fix: Resolve theme toggle FOUC issue on slow connections - Added critical inline CSS in BaseLayout.astro to style theme toggle immediately - Enhanced ThemeSwitcher skeleton state with proper sizing to prevent layout shift - Added id='theme-switcher-container' and 'theme-active' class for CSS targeting - Ensures theme toggle appears correctly sized from first paint - Prevents Flash of Unstyled Content (FOUC) on slow network connections - Includes comprehensive documentation in FOUC_FIX.md --- FOUC_FIX.md | 146 ++++++++++++++++++ .../src/components/theme/ThemeSwitcher.tsx | 29 +++- frontend/src/layouts/BaseLayout.astro | 72 +++++++++ 3 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 FOUC_FIX.md diff --git a/FOUC_FIX.md b/FOUC_FIX.md new file mode 100644 index 0000000000..0a02e73ddd --- /dev/null +++ b/FOUC_FIX.md @@ -0,0 +1,146 @@ +# Theme Toggle FOUC Fix + +## Problem +The dark mode/light mode theme toggle button appeared large and unstyled during initial page load on slow network connections, causing a "Flash of Unstyled Content" (FOUC). + +## Root Cause +The theme toggle button was rendering before the main CSS stylesheet (`global.css`) loaded. This caused the SVG icons to display at their default size without proper styling, creating a jarring visual experience. + +## Solution Implemented + +### 1. Critical CSS Inline Styles (BaseLayout.astro) +Added critical CSS directly in the `` section to ensure the theme toggle is styled immediately, even before Tailwind CSS loads: + +```html + +``` + +### 2. Improved ThemeSwitcher Component (ThemeSwitcher.tsx) + +#### Added ID for targeting +- Added `id="theme-switcher-container"` to both the skeleton and mounted states +- Added `theme-active` class to the active button state + +#### Enhanced Skeleton State +The skeleton now displays both light and dark mode buttons with proper sizing, preventing layout shift: + +```tsx +if (!mounted) { + return ( +
+
+
+ {/* Light mode icon with proper sizing */} +
+
+ {/* Dark mode icon with proper sizing */} +
+
+
+ ); +} +``` + +## Benefits + +1. **Instant Styling**: The theme toggle is styled immediately, even on slow connections +2. **No Layout Shift**: The skeleton state matches the final state exactly, preventing Cumulative Layout Shift (CLS) +3. **Consistent Experience**: Users see a properly sized button from the first paint +4. **Performance**: Critical CSS is minimal (~1KB) and doesn't block page rendering +5. **Responsive**: Works correctly across all device sizes (mobile, tablet, desktop) + +## Testing + +To verify the fix: + +1. **Throttle Network**: Open Chrome DevTools > Network tab > Throttle to "Slow 3G" +2. **Hard Refresh**: Press Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows) +3. **Observe**: The theme toggle should appear at its correct size immediately, without any flash or size change + +## Files Modified + +- `/Users/tirth/FreeDevTools/frontend/src/layouts/BaseLayout.astro` - Added critical inline CSS +- `/Users/tirth/FreeDevTools/frontend/src/components/theme/ThemeSwitcher.tsx` - Enhanced skeleton state and added IDs + +## Technical Notes + +- The critical CSS uses exact pixel values matching Tailwind's sizing system +- Responsive breakpoints match Tailwind's default breakpoints (sm: 640px, lg: 1024px) +- Dark mode styles are duplicated in critical CSS to ensure immediate application +- The `theme-active` class allows the inline CSS to target the active button state diff --git a/frontend/src/components/theme/ThemeSwitcher.tsx b/frontend/src/components/theme/ThemeSwitcher.tsx index 59b3e206c1..22a448e612 100644 --- a/frontend/src/components/theme/ThemeSwitcher.tsx +++ b/frontend/src/components/theme/ThemeSwitcher.tsx @@ -149,10 +149,27 @@ const ThemeSwitcher: React.FC = () => { // Don't render until mounted to prevent SSR issues if (!mounted) { return ( -
-
-
- {themeConfigs[1].icon} +
+
+
+ + + + + + + + + + + +
+
+ + + + +
@@ -165,7 +182,7 @@ const ThemeSwitcher: React.FC = () => { // ); return ( -
+
{ "p-0.5 *:size-4 sm:p-1 sm:*:size-5 lg:p-1 lg:*:size-6" } ${ theme === config.type - ? "bg-white ring ring-gray-950/10 dark:bg-gray-600 dark:ring-white/10" + ? "theme-active bg-white ring ring-gray-950/10 dark:bg-gray-600 dark:ring-white/10" : "hover:bg-gray-100 dark:hover:bg-gray-700" }`} onClick={() => handleThemeChange(config.type)} diff --git a/frontend/src/layouts/BaseLayout.astro b/frontend/src/layouts/BaseLayout.astro index 6281bda68d..5561d6f591 100644 --- a/frontend/src/layouts/BaseLayout.astro +++ b/frontend/src/layouts/BaseLayout.astro @@ -468,6 +468,78 @@ const GA_MEASUREMENT_ID = 'G-WXSDF484XZ'; + + +