Skip to content

fix(web): eliminate dark mode white flash on initial page load#188

Open
KaparthyReddy wants to merge 6 commits into
Dev-Card:mainfrom
KaparthyReddy:fix/dark-mode-flash
Open

fix(web): eliminate dark mode white flash on initial page load#188
KaparthyReddy wants to merge 6 commits into
Dev-Card:mainfrom
KaparthyReddy:fix/dark-mode-flash

Conversation

@KaparthyReddy
Copy link
Copy Markdown

Summary

Dark mode users experience a white flash during initial page load because the theme was previously applied inside onMount in Navbar.svelte, which runs after the first paint. This PR fixes the flash by injecting a blocking inline IIFE script into app.html that synchronously reads localStorage and window.matchMedia to apply the .dark class to <html> before any content renders.

Closes #91


Type of Change

  • Bug fix
  • New feature
  • Refactor (no functional change)
  • UI / Design change
  • Tests only
  • Documentation
  • Infrastructure / DevOps
  • Security

What Changed

  • apps/web/src/app.html — Added a blocking synchronous IIFE script in <head> that reads localStorage('devcard-theme') and falls back to window.matchMedia('prefers-color-scheme: dark'), applying .dark to <html> before first paint. Wrapped in try/catch for private browsing resilience.
  • apps/web/src/lib/components/Navbar.svelte — Removed localStorage read from onMount. Initial theme state 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 — Added window.matchMedia change event listener in onMount to dynamically sync theme when OS preference changes mid-session (only when no manual preference is saved in localStorage).

How to Test

  1. Run pnpm dev in apps/web
  2. Open http://localhost:5173 and switch to dark mode using the toggle
  3. Hard refresh the page (Cmd+Shift+R on Mac, Ctrl+Shift+R on Windows)
  4. Verify: page loads directly in dark mode with no white flash
  5. Switch OS system preference to dark while on the page with no saved preference — verify theme syncs automatically
  6. Verify light mode still works correctly by toggling back and hard refreshing

Checklist

  • My code follows the project's coding style (pnpm -r run lint passes).
  • TypeScript compiles without errors (pnpm -r run typecheck).
  • I have added or updated tests for the changes I made.
  • All tests pass locally (pnpm -r run test).
  • I have updated documentation where necessary.
  • No new console.log or debug statements left in the code.
  • Breaking changes are documented in this PR description.

Screenshots / Recordings

Before fix — white flash visible on hard refresh in dark mode:
image

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


Additional Context

The root cause was that onMount in 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.

Bhuva and others added 2 commits May 20, 2026 00:23
- 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
@KaparthyReddy KaparthyReddy force-pushed the fix/dark-mode-flash branch from e2012f7 to 369a503 Compare May 19, 2026 18:54
@Harxhit Harxhit added the gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. label May 19, 2026
Signed-off-by: Kaparthy Reddy <166050493+KaparthyReddy@users.noreply.github.com>
@KaparthyReddy
Copy link
Copy Markdown
Author

Hi @Harxhit!
Just wanted to give a gentle nudge. The PR is ready for review whenever you get a chance.
Happy to make any changes if needed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Dark mode users see a white flash on page load

3 participants