In today’s web, providing users with a pleasant browsing experience is essential. One popular way to achieve this is by adding a light/dark mode toggle to your website. In this blog post, I’ll walk you through implementing this functionality using NextJS and TailwindCSS. We’ll create a dropdown menu that allows users to choose between light, dark, and system modes.
Desired outcome
There are several ways to implement dark mode, but one of the best examples I’ve seen is how it’s done on the TailwindCSS docs. Users can choose between light, dark, and system mode, meaning they either manually select dark or light and have their preference saved to localStorage, or they choose to respect the OS setting.
I think this approach is the best of both worlds, as users have the flexibility to select a specific mode for each website or tool they use. The desired functionality involves a dropdown with three settings and corresponding logos. When users manually select dark or light mode, a colored logo will indicate the chosen setting. By default, the system mode should be selected.
Tools for implementation
To implement this functionality, we will use the following tools:
- TailwindCSS: A utility-first CSS framework for rapid UI development
- NextJS: A React framework for server-rendered applications
- next-themes: A plugin by Paco Coursey that provides the
useTheme()
hook
Steps for implementation
- First make sure all dependencies are installed:
pnpm install tailwindcss next next-themes
- Wrap your application with the ThemeProvider component provided by
next-themes
in your_app.tsx
file:
import { ThemeProvider } from "next-themes";
function App({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}
export default App;
- The useTheme hook provides the current theme (
theme
) and a function to toggle the theme (setTheme
). You can use these values to customize the appearance of your components based on the selected theme.
import { useTheme } from "next-themes";
function Component() {
const { theme, setTheme } = useTheme();
function toggleTheme() {
setTheme(theme === "dark" ? "light" : "dark");
}
return (
<div>
<button onClick={toggleTheme}>Toggle theme</button>
<p>Current theme: {theme}</p>
</div>
);
}
export default Component;
Example for implementation
This is a working example that recreates the functionality of the TailwindCSS docs. This example uses Radix UI for the dropdown menu.
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import * as ToggleGroup from "@radix-ui/react-toggle-group";
import { IconDeviceLaptop, IconMoon, IconSun } from "@tabler/icons-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
function ThemeDropdown() {
const [mounted, setMounted] = useState(false);
const { theme, resolvedTheme, setTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{resolvedTheme === "light" && theme === "light" ? (
<IconSun size={30} className="text-blue-400" />
) : resolvedTheme === "light" ? (
<IconSun size={30} />
) : resolvedTheme === "dark" && theme === "dark" ? (
<IconMoon size={30} className="text-blue-300" />
) : resolvedTheme === "dark" ? (
<IconMoon size={30} />
) : (
<IconDeviceLaptop size={30} />
)}
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Label />
<DropdownMenu.Item />
<ToggleGroup.Root
type="single"
orientation="vertical"
value={theme}
aria-label="Dark/Light/System Mode Selection"
className="flex flex-col items-start w-full gap-1 pt-1 m-2 border-2 rounded-md"
>
<ToggleGroup.Item
value="light"
onClick={() => setTheme("light")}
className={`flex items-center w-full gap-1 px-1 py-1 mx-0 hover:bg-hover-light dark:hover:bg-hover-dark ${
theme === "light" ? "font-bold" : "font-normal"
}`}
>
<IconSun size={30} />
<span>Light</span>
</ToggleGroup.Item>
<ToggleGroup.Item
value="dark"
onClick={() => setTheme("dark")}
className={`flex items-center w-full gap-1 px-1 py-1 mx-0 hover:bg-hover-light dark:hover:bg-hover-dark ${
theme === "dark" ? "font-bold" : "font-normal"
}`}
>
<IconMoon size={30} />
<span>Dark</span>
</ToggleGroup.Item>
<ToggleGroup.Item
value="system"
onClick={() => setTheme("system")}
className={`flex items-center w-full gap-1 px-1 py-1 mx-0 hover:bg-hover-light dark:hover:bg-hover-dark ${
theme === "system" ? "font-bold" : "font-normal"
}`}
>
<IconDeviceLaptop size={30} />
<span>System</span>
</ToggleGroup.Item>
</ToggleGroup.Root>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</>
);
}
export default ThemeDropdown;
The resolvedTheme
variable is used to check the currently selected theme(light/dark/system). By default, the system mode is selected, and the logo associated with it is displayed. When users manually choose between dark or light mode, the corresponding logo will indicate the selected setting.
Conclusion
In this tutorial, we have explored how to implement a light/dark mode toggle using NextJS and TailwindCSS. By giving users the flexibility to choose between light, dark, and system modes, we can enhance the user experience by catering to their individual preferences.