Enhancement Request Template
Is your enhancement request related to a problem? Please describe.
Yes. When toggling the app theme (light ↔ dark) users can observe an abrupt flash/jump in background and text colors (FOUC) and the toggle icon can visibly glitch if clicked rapidly. This results in a jarring visual experience and can sometimes surface hydration mismatch warnings in SSR contexts.
Describe the enhancement you'd like
Make theme switching smooth and robust by:
- Adding a short CSS transition (≈300ms) for background and text color on the root document element so theme changes animate instead of jumping.
- Temporarily disabling the toggle and applying a small "isChanging" state during the transition to prevent rapid double toggles and race conditions.
- Using
framer-motion to animate the toggle icon (smooth rotate/fade) with a controlled transition.
- Keeping the SSR-mounted check to avoid hydration mismatch (render the client-only toggle only after mount).
Describe alternatives you've considered
- Applying transitions via global CSS classes. This works but can be overridden by other global rules; applying the transition programmatically to
document.documentElement keeps the change explicit and scoped.
- Letting
next-themes handle transitions implicitly. next-themes doesn't add transitions by default, so user-observed flashes will persist without a supplemental approach.
- Rendering a static placeholder icon during hydration to avoid flash. This reduces flash but doesn't provide the smooth animation experience.
Possible Implementation Details
Suggested changes in components/shared/theme-toggle.tsx:
const [isChanging, setIsChanging] = useState(false);
setIsChanging(true);
setTheme(theme === "light" ? "dark" : "light");
setTimeout(() => setIsChanging(false), 300);
- On mount, add a temporary transition:
useEffect(() => {
document.documentElement.style.transition = "background-color 0.3s ease-in-out, color 0.3s ease-in-out";
return () => { document.documentElement.style.transition = ""; };
}, []);
- Use
framer-motion for the icon and disable the button while isChanging is true:
<motion.button disabled={isChanging} className={isChanging ? 'opacity-50' : ''}>
<motion.div animate={{ rotate: theme === 'light' ? 0 : 180 }} transition={{ duration: 0.3 }}>
{theme === 'light' ? <Moon/> : <Sun/>}
</motion.div>
</motion.button>
- Ensure
aria-label stays present and consider adding aria-busy={isChanging} to help assistive technologies.
Additional context
- Files to check:
components/shared/theme-toggle.tsx
- Theme provider usage (likely
ThemeProvider from next-themes)
- If the app later supports multiple themes (beyond light/dark), update toggle logic accordingly.
- Possible global CSS rules that set
transition: none could block the effect — verify global styles.
Optional Sections (if relevant):
- Priority: Medium — improves perceived polish and UX.
- Are you willing to submit a PR for this enhancement? Yes (I already applied a fix in the repo; can open a PR).
Enhancement Request Template
Is your enhancement request related to a problem? Please describe.
Yes. When toggling the app theme (light ↔ dark) users can observe an abrupt flash/jump in background and text colors (FOUC) and the toggle icon can visibly glitch if clicked rapidly. This results in a jarring visual experience and can sometimes surface hydration mismatch warnings in SSR contexts.
Describe the enhancement you'd like
Make theme switching smooth and robust by:
framer-motionto animate the toggle icon (smooth rotate/fade) with a controlled transition.Describe alternatives you've considered
document.documentElementkeeps the change explicit and scoped.next-themeshandle transitions implicitly.next-themesdoesn't add transitions by default, so user-observed flashes will persist without a supplemental approach.Possible Implementation Details
Suggested changes in
components/shared/theme-toggle.tsx:framer-motionfor the icon and disable the button whileisChangingis true:aria-labelstays present and consider addingaria-busy={isChanging}to help assistive technologies.Additional context
components/shared/theme-toggle.tsxThemeProviderfromnext-themes)transition: nonecould block the effect — verify global styles.Optional Sections (if relevant):