fix(web): eliminate dark mode white flash on initial page load#188
Open
KaparthyReddy wants to merge 6 commits into
Open
fix(web): eliminate dark mode white flash on initial page load#188KaparthyReddy wants to merge 6 commits into
KaparthyReddy wants to merge 6 commits into
Conversation
…ility improvements
- Add blocking IIFE script in app.html that runs synchronously before first paint, reads localStorage preference and system preference via window.matchMedia, and applies .dark class to <html> immediately - Refactor Navbar.svelte theme initialization to read the class already applied by the blocking script instead of re-reading localStorage in onMount, preventing state mismatch and redundant DOM writes - Add system preference change listener in onMount to dynamically sync theme when OS preference changes mid-session (only when no manual preference is saved) - Wrap localStorage access in try/catch for private browsing resilience
e2012f7 to
369a503
Compare
Signed-off-by: Kaparthy Reddy <166050493+KaparthyReddy@users.noreply.github.com>
Author
|
Hi @Harxhit! |
Signed-off-by: Kaparthy Reddy <166050493+KaparthyReddy@users.noreply.github.com>
…y/DevCard into fix/dark-mode-flash
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Dark mode users experience a white flash during initial page load because the theme was previously applied inside
onMountinNavbar.svelte, which runs after the first paint. This PR fixes the flash by injecting a blocking inline IIFE script intoapp.htmlthat synchronously readslocalStorageandwindow.matchMediato apply the.darkclass to<html>before any content renders.Closes #91
Type of Change
What Changed
apps/web/src/app.html— Added a blocking synchronous IIFE script in<head>that readslocalStorage('devcard-theme')and falls back towindow.matchMedia('prefers-color-scheme: dark'), applying.darkto<html>before first paint. Wrapped in try/catch for private browsing resilience.apps/web/src/lib/components/Navbar.svelte— Removed localStorage read fromonMount. Initialthemestate now reads from the class already applied by the blocking script (document.documentElement.classList.contains('dark')), preventing state mismatch and redundant DOM writes.apps/web/src/lib/components/Navbar.svelte— Addedwindow.matchMediachangeevent listener inonMountto dynamically sync theme when OS preference changes mid-session (only when no manual preference is saved in localStorage).How to Test
pnpm devinapps/webhttp://localhost:5173and switch to dark mode using the toggleCmd+Shift+Ron Mac,Ctrl+Shift+Ron Windows)Checklist
pnpm -r run lintpasses).pnpm -r run typecheck).pnpm -r run test).console.logor debug statements left in the code.Screenshots / Recordings
Before fix — white flash visible on hard refresh in dark mode:

After fix — page loads directly in dark mode, no flash:

Additional Context
The root cause was that
onMountin SvelteKit runs client-side after hydration, meaning the browser renders the default light styles first. The standard solution (used by next-themes, Remix, etc.) is a render-blocking script that runs synchronously in<head>before the body is parsed. This script is intentionally not deferred or async — it must block to prevent the flash.