App Directory dark mode with tailwindcss and SSR #47952
Replies: 8 comments 21 replies
-
@artemavrin Thanks for this. So clarifying the obvious, but this is better than next themes for example because it doesn't require wrapping the whole app in a provider, which necessitates the whole app needing to be "use client", meaning the whole app won't be server rendered defeating the whole purpose of the app dir right? This approach avoids the whole app being wrapped in a provider, thus, allows for server side rendering cuz the server can get the theme cookie(?) when rendering on the server side and still thus allows for theme switching right? |
Beta Was this translation helpful? Give feedback.
-
What happens if you use |
Beta Was this translation helpful? Give feedback.
-
I was looking for a way to replace |
Beta Was this translation helpful? Give feedback.
-
Thanks for your solution, But this wont work for SSG, I am looking for a theme solution based on the localstorage, which will work for both client render and server render. For now, i am trying to set the theme in the useEffect logic, but at that moment, the html has been rendered. So updating the theme in useEffect will cause the page to flicker, which is a bad user experience. After reading daisyui doc website, the theme logic in it is based on the localstorage like this: <script>
try {
document.documentElement.setAttribute("data-theme", localStorage.getItem("theme"))
} catch (e) {}
</script> So i hope to add a script in |
Beta Was this translation helpful? Give feedback.
-
does that work? , emitToDocumentHead() {
const cookie_theme = requestContext?.cookie?.theme;
return `
<link rel="icon" type="image/svg+xml" href="/site.svg" />
<script>
(function() {
document.documentElement.setAttribute("data-theme", "${cookie_theme}");
})();
</script>
`
}, but I think that should work if it is the first script that runs on the page |
Beta Was this translation helpful? Give feedback.
-
where this icons from ? |
Beta Was this translation helpful? Give feedback.
-
Nice! This is a very easy way to set up the theme switching. I think it can be simplified a little like so: "use client";
import { Moon as MoonIcon, SunMedium as SunIcon } from 'lucide-react';
const ToggleThemeButton = ({ className = '' }: { className?: string }) => {
const toggleTheme = () => {
const isDark = document.documentElement.classList.toggle('dark');
// setting SameSite property to satisfy relevant console warning. Use SameSite=None if site relies on cross-site requests
document.cookie = `theme=${isDark ? 'dark' : 'light'}; SameSite=Lax; Path=/;`;
}
return (
<button
onClick={toggleTheme}
className={`p-2 bg-gray-200 dark:bg-gray-800 rounded-full ${className}`}
aria-label="Toggle theme"
>
<SunIcon size="24" className="text-gray-200 hidden dark:block" />
<MoonIcon size="24" className="text-gray-800 block dark:hidden" />
</button>
)
} Then all you need for everything to work is the change in layout.tsx. Because the icons are controlled with CSS, you don't need to use cookies wherever you choose to import the theme toggle. Done! The only problem is, this is unable to detect the user theme preference. To do that while avoiding a Flash of Unstyled Content (FOUC) I'm pretty sure the only way is to inject a script in the header. Since this is getting information unavailable to the server and updating the html we will get a hydration warning, which we can suppress with "suppressHydrationWarning". export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const storedTheme = cookies().get("theme")?.value;
return (
<html lang="en" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
if (${storedTheme === 'dark'} || (${!storedTheme} && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
})();
`,
}}
/>
</head>
<body className={`${inter.className} bg-white dark:bg-gray-900`}>
<ToggleThemeButton className="absolute top-6 right-6" />
{children}
</body>
</html>
);
} If you want to use localStorage instead of cookies, as some comments have mentioned, it's easy to switch out the above with the following: const toggleTheme = () => {
const isDark = document.documentElement.classList.toggle('dark');
- document.cookie = `theme=${isDark ? 'dark' : 'light'}; SameSite=Lax; Path=/;`;
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
}; - const storedTheme = cookies().get("theme")?.value;
+ const theme = localStorage.getItem('theme'); // inside the script
+ if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
- if (${storedTheme === 'dark'} || (${!storedTheme} && window.matchMedia('(prefers-color-scheme: dark)').matches)) { |
Beta Was this translation helpful? Give feedback.
-
Dark mode in app dir with tailwindcss and SSR
source code
Beta Was this translation helpful? Give feedback.
All reactions