Skip to content

fix: Theme toggle icon syncs correctly with stored theme on page reload#25

Open
pranjal29092005 wants to merge 2 commits intoStabilityNexus:mainfrom
pranjal29092005:fix/theme-toggle-icon-sync-on-reload
Open

fix: Theme toggle icon syncs correctly with stored theme on page reload#25
pranjal29092005 wants to merge 2 commits intoStabilityNexus:mainfrom
pranjal29092005:fix/theme-toggle-icon-sync-on-reload

Conversation

@pranjal29092005
Copy link

@pranjal29092005 pranjal29092005 commented Dec 13, 2025

🐛 Problem

compressed_Screen.Recording.2025-12-13.223816.mp4

Fixes #5

When users activate light mode and reload the page:

  • ✅ Theme persists correctly (light mode stays active)
  • ❌ Toggle icon shows moon instead of sun

🔍 Root Cause

The placeholder component (shown during hydration) had a hardcoded Sun icon with opacity-100 that didn't reflect the stored theme preference in localStorage.

✨ Solution

  • Updated placeholder to show neutral state during hydration (both icons hidden)
  • After mounting, correct icon immediately appears based on stored theme
  • Maintains compatibility with next-themes SSR/hydration strategy

📁 Changes

  • Modified components/toggle-theme.tsx placeholder state (line 42)
  • Changed Sun icon from opacity-100 to opacity-0
  • 1 file changed, 1 line modified
  • No breaking changes, no API changes

✅ Testing

Tested on Chrome, Firefox, and Safari:

  • Light mode → reload → sun icon shows ✅
  • Dark mode → reload → moon icon shows ✅
  • System theme works correctly ✅
  • Toggle functionality unchanged ✅
  • No hydration warnings ✅

📸 Screenshots

Before Fix

Light mode persisted, but toggle showed incorrect moon icon after reload.

After Fix - Light Mode

Sun icon correctly persists after reloading in light mode.

After Fix - Dark Mode

Moon icon correctly persists after reloading in dark mode.


Type: Bug Fix
Impact: User Experience
Stability: High - Minimal change, well-tested
Breaking Changes: None
Dependencies: No new dependencies added
Screenshot 2025-12-13 223329
Screenshot 2025-12-13 223415
Screenshot 2025-12-13 223437
Screenshot 2025-12-13 223457

Summary by CodeRabbit

  • Bug Fixes
    • Fixed initial visibility of the theme icons so they no longer appear incorrectly on first load.
  • Accessibility
    • Non-interactive toggle states are now hidden from assistive tech until the control is active.
  • Quality / UX
    • Theme toggle is now reliably clickable when ready and features smoother icon transitions during switching.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

Walkthrough

Toggle component rendering was adjusted: the unmounted render uses aria-hidden="true" and hides the Sun icon (opacity-0); the mounted render attaches onClick (toggleTheme), restores aria-label/role, and unifies Sun/Moon opacity toggling and knob translation based on currentTheme.

Changes

Cohort / File(s) Summary
Theme toggle render + wiring
components/toggle-theme.tsx
Reworked unmounted vs mounted markup: unmounted wrapper now uses aria-hidden="true" (removed aria-label/role), Sun icon initial opacity changed to opacity-0; mounted wrapper binds onClick (toggleTheme) and keeps aria-label/role. Unified Sun/Moon opacity conditional expressions and preserved knob translation logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Focus review on components/toggle-theme.tsx for accessibility attribute changes, click-handler wiring, and combined opacity expressions.
  • Verify hydration behavior on initial render and that ARIA semantics remain correct when mounted/unmounted.

Possibly related PRs

Suggested reviewers

  • Zahnentferner

Poem

🐇 I nudge the knob with tiny feet,
Hiding the sun till mounts complete.
A gentle hop from dark to bright,
I blink my eyes and set it right.
Toggle done — the theme feels sweet. ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: theme toggle icon synchronization with stored theme on page reload, directly addressing the core issue.
Linked Issues check ✅ Passed The PR fully addresses issue #5 by fixing the placeholder component's hardcoded Sun icon opacity, ensuring the correct icon displays based on stored theme preference after reload.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the theme toggle icon sync issue; only the toggle-theme.tsx file is modified with a single targeted fix to the placeholder component.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd4164b and d9c353d.

📒 Files selected for processing (1)
  • components/toggle-theme.tsx (2 hunks)
🔇 Additional comments (3)
components/toggle-theme.tsx (3)

34-52: Excellent fix for the theme toggle hydration bug!

The changes to the unmounted state correctly address the issue:

  1. Line 41 - Sun icon opacity-0: This is the key fix. By hiding both icons during SSR/hydration, you prevent the mismatch where the placeholder showed a sun icon even when the stored theme was dark. The neutral state ensures no misleading visual feedback until the component mounts.

  2. Line 36 - aria-hidden="true": Appropriate accessibility improvement. Since the placeholder has no onClick handler and is purely visual during hydration, hiding it from assistive technologies is correct. The mounted state properly exposes interactive semantics.

  3. Line 47 - Explicit size={24}: Good consistency - both Sun and Moon now have explicit sizing.

The approach aligns with next-themes best practices: show a neutral placeholder during SSR/hydration, then reveal the correct theme-dependent UI after mounting.


56-84: Mounted state implementation looks solid.

The interactive toggle is properly implemented:

  • Lines 58-60: Correct event handling and accessibility attributes (onClick, aria-label, role="button")
  • Lines 64-67, 71-72, 77-78: Template literal formatting improved for readability; the conditional logic correctly toggles icon opacity and knob position based on currentTheme

The mounted state correctly reflects the stored theme preference using resolvedTheme || theme, ensuring the sun icon appears for light mode and moon for dark mode.


1-85: Well-executed bug fix with excellent attention to detail.

This PR successfully resolves the theme toggle icon sync issue with a minimal, targeted change. The solution demonstrates good understanding of React hydration, next-themes patterns, and accessibility best practices:

Root cause correctly identified and fixed - changing the unmounted Sun icon to opacity-0 eliminates the hardcoded visual state
Accessibility improved - aria-hidden="true" on non-interactive placeholder is appropriate
Code quality enhanced - consistent formatting and explicit sizing
No regressions - mounted state preserves all interactive functionality
Testing comprehensive - verified across Chrome, Firefox, and Safari per PR description

The brief neutral state during hydration is a correct tradeoff to prevent showing misleading icons. Great work!


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
components/toggle-theme.tsx (1)

32-54: Optional: Consider accessibility attributes on unmounted placeholder.

The unmounted placeholder includes role="button" and aria-label="Toggle theme" but has no click handler. While this state is very brief (only during hydration), you might consider:

  • Removing role="button" and aria-label from the placeholder, or
  • Adding aria-hidden="true" to indicate it's not interactive

This is a minor refinement since the placeholder appears for only milliseconds during hydration, and the lack of tabIndex already prevents keyboard interaction.

Example if you want to refine:

       <div
         className="flex items-center space-x-4"
-        aria-label="Toggle theme"
-        role="button"
+        aria-hidden="true"
       >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72f97c5 and fd4164b.

📒 Files selected for processing (1)
  • components/toggle-theme.tsx (1 hunks)
🔇 Additional comments (1)
components/toggle-theme.tsx (1)

42-42: Perfect fix for the theme icon sync issue.

Changing the Sun icon to opacity-0 in the unmounted placeholder ensures both icons are hidden during hydration. This prevents the incorrect icon flash when users reload the page with a stored theme preference. After mounting, the correct icon appears immediately based on resolvedTheme.

The solution is minimal, maintains DOM structure consistency for hydration, and aligns with next-themes SSR best practices.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The light/dark mode toggle is behaving strangely when the page is reloaded

2 participants