Fix dark-mode flash on page navigation; respect OS theme on first load - #243
Open
Trosper3 wants to merge 3 commits into
Open
Fix dark-mode flash on page navigation; respect OS theme on first load#243Trosper3 wants to merge 3 commits into
Trosper3 wants to merge 3 commits into
Conversation
Replaces the hand-rolled ThemeModeContext (which always initialized to
light and only applied a saved dark preference in a post-mount effect,
causing a visible flash for returning dark-mode users on every full
page load) with next-themes. Its blocking inline script sets the theme
class on <html> before React hydrates, and resolves the OS-level
prefers-color-scheme on a visitor's first-ever load instead of
defaulting to light.
ThemeModeContext.js is now a thin MUI bridge: it reads next-themes'
resolvedTheme and builds the MUI theme from it, gated on mount so
server and pre-mount client renders match (no hydration mismatch, and
no MUI component is ever painted with the wrong-mode colors). Critical
CSS in globals.css colors the body correctly during that brief
pre-mount window, keyed off the same html.dark class the blocking
script sets.
The useThemeMode() hook keeps its existing { mode, toggleMode } shape,
so all 17 existing call sites (including the toggle button in
ResponsiveAppBar) work unchanged.
Redux Build System — CI Report
Overall: ❌ 1 passed · 4 failed · 2 skipped · 1 blocked ❌ audit — 1 high · 1 moderate
❌ format-check — 60 format · 33 import order
❌ lint — 24 errors, 41 warnings
|
Avoids the react-hooks/set-state-in-effect lint error while keeping the same SSR-safe "unmounted -> null" gating behavior.
sectionCardSx/innerCardSx (and similar) use transition: all for a smooth hover effect, but background/border/shadow all differ between light and dark variants too -- so toggling animated through every in-between color over ~0.2-0.25s, most visibly on hover-highlighted elements. Transitions are now forced off for one frame around the toggle itself so the swap is instant, then re-enabled immediately after for normal hover animations.
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.
Switches the site's light/dark theming from a hand-rolled React context to
next-themes, so returning dark-mode users no longer see a flash of light mode on every page load, and first-time visitors get their OS theme instead of a hardcoded light default. Also fixes a related flash that happens when using the light/dark toggle itself.Features
What to Check
Issue Handling
Closes #240
Verification (technical detail)
npm run lintandnpm run buildboth pass; no new errors or warnings in any file touched by this change (all lint errors/warnings present in the repo are pre-existing and in unrelated files).react-hooks/set-state-in-effectlint error on the mount-detection gate by replacinguseState+useEffectwithuseSyncExternalStore, which gives the same server-false/client-true value without ever callingsetStateinside an effect.next-themes's blocking inline script is present at the very top of<body>on every route checked (/,/browse), and runs before any other content — it reads the stored preference (or resolvesprefers-color-schemeif nothing is stored) and sets thedark/no class on<html>synchronously.styles/globals.css(html.dark, html.dark body { ... }) compiled correctly into the render-blocking stylesheet<link>loaded in<head>, so it's in effect before the first paint.document.documentElement's classList or otherwise fights thedarkclass next-themes sets.redux-theme-modelocalStorage key was read/written only inside the file being replaced, so removing it doesn't silently break anything else.sectionCardSx/innerCardSx(theme.js) usetransition: allfor a smooth hover effect, and every one of those properties (background/border/shadow) also differs between light/dark variants. Toggling now briefly disables all CSS transitions for one frame around the swap (.theme-transition-offin globals.css, wired intotoggleModeinThemeModeContext.js), so the color change is instant instead of animating through it, while normal hover transitions resume immediately after.