-
Notifications
You must be signed in to change notification settings - Fork 19
Fix: Resolve theme toggle FOUC issue on slow connections #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
TirthDhandhukia30
wants to merge
2
commits into
HexmosTech:main
Choose a base branch
from
TirthDhandhukia30:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 `<head>` section to ensure the theme toggle is styled immediately, even before Tailwind CSS loads: | ||
|
|
||
| ```html | ||
| <style> | ||
| /* Prevent theme toggle FOUC by inlining critical styles */ | ||
| #theme-switcher-container { | ||
| display: grid; | ||
| grid-template-columns: 1fr; | ||
| } | ||
| /* Container styling with responsive padding */ | ||
| #theme-switcher-container > div { | ||
| position: relative; | ||
| z-index: 0; | ||
| display: inline-grid; | ||
| gap: 0.125rem; | ||
| border-radius: 9999px; | ||
| background-color: rgba(0, 0, 0, 0.05); | ||
| padding: 0.125rem; | ||
| color: rgb(9, 9, 11); | ||
| } | ||
| /* Dark mode styles */ | ||
| .dark #theme-switcher-container > div { | ||
| background-color: rgba(255, 255, 255, 0.1); | ||
| color: rgb(255, 255, 255); | ||
| } | ||
| /* Button/icon container sizing */ | ||
| #theme-switcher-container button, | ||
| #theme-switcher-container > div > div { | ||
| position: relative; | ||
| border-radius: 9999px; | ||
| cursor: pointer; | ||
| transition: all 0.2s; | ||
| padding: 0.125rem; | ||
| } | ||
| /* SVG icon sizing - responsive */ | ||
| #theme-switcher-container svg { | ||
| width: 1rem; | ||
| height: 1rem; | ||
| } | ||
| /* Tablet sizing */ | ||
| @media (min-width: 640px) { | ||
| #theme-switcher-container > div { | ||
| padding: 0.1875rem; | ||
| } | ||
| #theme-switcher-container button, | ||
| #theme-switcher-container > div > div { | ||
| padding: 0.25rem; | ||
| } | ||
| #theme-switcher-container svg { | ||
| width: 1.25rem; | ||
| height: 1.25rem; | ||
| } | ||
| } | ||
| /* Desktop sizing */ | ||
| @media (min-width: 1024px) { | ||
| #theme-switcher-container svg { | ||
| width: 1.5rem; | ||
| height: 1.5rem; | ||
| } | ||
| } | ||
| /* Active theme button styling */ | ||
| #theme-switcher-container .theme-active { | ||
| background-color: rgb(255, 255, 255); | ||
| box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); | ||
| } | ||
| .dark #theme-switcher-container .theme-active { | ||
| background-color: rgb(75, 85, 99); | ||
| box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1); | ||
| } | ||
| </style> | ||
| ``` | ||
|
|
||
| ### 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 ( | ||
| <div id="theme-switcher-container" className="grid grid-cols-1"> | ||
| <div className="relative z-0 inline-grid grid-cols-2 gap-0.5 rounded-full bg-gray-950/5 p-0.5 sm:p-0.75 text-gray-950 dark:bg-white/10 dark:text-white"> | ||
| <div className="relative rounded-full p-0.5 sm:p-1 lg:p-1 theme-active"> | ||
| {/* Light mode icon with proper sizing */} | ||
| </div> | ||
| <div className="relative rounded-full p-0.5 sm:p-1 lg:p-1"> | ||
| {/* Dark mode icon with proper sizing */} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -149,10 +149,27 @@ const ThemeSwitcher: React.FC = () => { | |
| // Don't render until mounted to prevent SSR issues | ||
| if (!mounted) { | ||
| return ( | ||
| <div className="grid grid-cols-1"> | ||
| <div className="relative z-0 inline-grid grid-cols-2 md:grid-cols-3 gap-0.5 rounded-full bg-gray-950/5 p-0.75 text-gray-950 dark:bg-white/10 dark:text-white"> | ||
| <div className="relative rounded-full p-1.5 *:size-7 bg-white ring ring-gray-950/10 sm:p-0"> | ||
| {themeConfigs[1].icon} | ||
| <div id="theme-switcher-container" className="grid grid-cols-1"> | ||
| <div className="relative z-0 inline-grid grid-cols-2 gap-0.5 rounded-full bg-gray-950/5 p-0.5 sm:p-0.75 text-gray-950 dark:bg-white/10 dark:text-white"> | ||
| <div className="relative rounded-full p-0.5 sm:p-1 lg:p-1 theme-active"> | ||
| <svg viewBox="0 0 28 28" fill="none" className="size-4 sm:size-5 lg:size-6"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is hardcoding the svg in file required? We have many lucide icons, Ex: https://hexmos.com/freedevtools/svg_icons/comet/moon.svg |
||
| <circle cx="14" cy="14" r="3.5" stroke="currentColor"></circle> | ||
| <path d="M14 8.5V6.5" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M17.889 10.1115L19.3032 8.69727" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M19.5 14L21.5 14" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M17.889 17.8885L19.3032 19.3027" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M14 21.5V19.5" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M8.69663 19.3029L10.1108 17.8887" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M6.5 14L8.5 14" stroke="currentColor" strokeLinecap="round"></path> | ||
| <path d="M8.69663 8.69711L10.1108 10.1113" stroke="currentColor" strokeLinecap="round"></path> | ||
| </svg> | ||
| </div> | ||
| <div className="relative rounded-full p-0.5 sm:p-1 lg:p-1"> | ||
| <svg viewBox="0 0 28 28" fill="none" className="size-4 sm:size-5 lg:size-6"> | ||
| <path d="M10.5 9.99914C10.5 14.1413 13.8579 17.4991 18 17.4991C19.0332 17.4991 20.0176 17.2902 20.9132 16.9123C19.7761 19.6075 17.109 21.4991 14 21.4991C9.85786 21.4991 6.5 18.1413 6.5 13.9991C6.5 10.8902 8.39167 8.22304 11.0868 7.08594C10.7089 7.98159 10.5 8.96597 10.5 9.99914Z" stroke="currentColor" strokeLinejoin="round"></path> | ||
| <path d="M16.3561 6.50754L16.5 5.5L16.6439 6.50754C16.7068 6.94752 17.0525 7.29321 17.4925 7.35607L18.5 7.5L17.4925 7.64393C17.0525 7.70679 16.7068 8.05248 16.6439 8.49246L16.5 9.5L16.3561 8.49246C16.2932 8.05248 15.9475 7.70679 15.5075 7.64393L14.5 7.5L15.5075 7.35607C15.9475 7.29321 16.2932 6.94752 16.3561 6.50754Z" fill="currentColor" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round"></path> | ||
| <path d="M20.3561 11.5075L20.5 10.5L20.6439 11.5075C20.7068 11.9475 21.0525 12.2932 21.4925 12.3561L22.5 12.5L21.4925 12.6439C21.0525 12.7068 20.7068 13.0525 20.6439 13.4925L20.5 14.5L20.3561 13.4925C20.2932 13.0525 19.9475 12.7068 19.5075 12.6439L18.5 12.5L19.5075 12.3561C19.9475 12.2932 20.2932 11.9475 20.3561 11.5075Z" fill="currentColor" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round"></path> | ||
| </svg> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
@@ -165,7 +182,7 @@ const ThemeSwitcher: React.FC = () => { | |
| // ); | ||
|
|
||
| return ( | ||
| <div className="grid grid-cols-1"> | ||
| <div id="theme-switcher-container" className="grid grid-cols-1"> | ||
| <div | ||
| className="relative z-0 inline-grid gap-0.5 rounded-full bg-gray-950/5 p-0.5 sm:p-0.75 text-gray-950 dark:bg-white/10 dark:text-white" | ||
| style={{ | ||
|
|
@@ -180,7 +197,7 @@ const ThemeSwitcher: React.FC = () => { | |
| "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)} | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pls add the description in PR itself and rm this file.