Hello Devs,
Nowadays, one feature that you will see in almost all sites especially in blogs is theme switching specifically dark mode.
And that's what we are building today. [Dark Mode in NextJs page with Tailwind and Shadcn-UI]
So, let's dive in to build our third front-end feature.
What are we building?
Live preview with code: https://stackblitz.com/edit/stackblitz-starters-adalin?file=app%2Fpage.tsx
Before we start I just wanna mention that:
- This post is the #3 issue of my newsletter.
- Where I am building one complex front-end feature every week and breaking down the exact steps, philosophy, and system design behind it.
- Join the newsletter to learn with me.
Philosophy
First, let's understand the philosophy and basic system design behind adding dark mode to a web page.
As front-end engineers, we should provide our users with the best experience possible. Readability and simplicity are important. There is no point in creating the best animation in the world which leaves your user confused.
Remember, The goal is to be clear, not clever.
Dark mode provides relief to the user's eyes. You as a developer must be using the dark theme of VS-Code. Then why deprive your users of that experience?
System Design
You must have seen the color wheel.
The color wheel provides a complimentary color for each primary color. You will use the color wheel if you are building multiple themes on your page.
But for our purpose, we only care about two colors white (primary) and black (secondary). I have oversimplified the design by saying white (primary) and black (secondary) colors.
We will be using different shades of those colors using hue and saturation as you will see later.
We will predefine all our primary and their respective secondary colors and whenever the user switches to dark mode all we have to do is switch the primary and secondary colors being used in our code.
Let the coding begin.
Let's start by creating a new NextJs project using create-next-app. Create-next-app is equivalent to create-react-app but for NextJs.
npx create-next-app@latest next dark --typescript --tailwind
This will create a new NextJs project named nextDark
with typescript and tailwind already configured for us. Now replace the default content in app/page.tsx
with the following code.
page.tsx is a special file in NextJs that serves as the home page.
page.tsx
import { Button } from '@/components/ui/button';
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center p-24">
<h1 className="text-2xl font-bold">NextJs Dark Mode</h1>
<p>Issue #3 of the Newsletter.</p>
<small className="my-4">
I am building one complex frontend feature every week. Breaking down the
exact thought process and system design behind it.
</small>
<a href="https://diaryofadev.ck.page" target="_blank">
<Button>Join The Newsletter</Button>
</a>
</main>
);
}
The button you see in the above code is not a regular button it is a shadcn-ui component. Shadcn is a UI components library. Let's add it to our project.
Run the following two commands in your terminal. Run them one by one.
npx shadcn-ui@latest init // answer few config questions, go with default ones.
​npx shadcn-ui@latest add button
The first command installs shadcn-ui, its dependencies latest version. Sets up the project configuration like tailwind.config.ts and app/globals.css
.
The second command add the button component in components/ui/button.tsx
.
Ok, our home page is ready to go.
See the globals.css file, it is modified by shadcn-ui. It contains some interesting configurations.
globals.css
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
The class .dark
defines the secondary color for each respective primary color. Check the global CSS file and explore the primary and their secondary colors for a while. This makes our job to switch to light/dark mode very easy.
Adding dark mode.
Install next-themes
. Next-themes provides a wrapper to wrap the part of the application where we want theme switching. We will wrap up our entire application.
npm install next-themes
Create a theme provider (the wrapper). Create a new file theme-provider.tsx
and paste the following code.
theme-provider.tsx
'use client';
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
export function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
Make sure to add "use client" at top of the file because this must be a client component not server component. Then we import ThemeProvider from "next-themes" and return it with children.
Next let's wrap the root layout with ThemeProvider. app/layout.tsx
is again a special file in NextJs. The style and content in layout is applied to the entire app.
So, wrapping layout with ThemeProvider will add theme-switching to the entire app.
layouts.tsx
return (
<html lang="en">
<body className={inter.className}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<ModeToggle />
{children}
</ThemeProvider>
</body>
</html>
);
Okay, now all we need to do is add a theme toggle button. Notice in the above code there is a component <ModeToggle />
. Let's create that component and we will be done.
modle-toggle.jsx
'use client';
import * as React from 'react';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
export function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
The above code adds a dropdown menu to switch between light, dark, and system. The dropdown is a shadcn-ui component. So let's add that component to our project.
Run the following command.
npx shadcn-ui@latest add dropdown-menu
This will add dropdown-menu component in component/ui/dropdown-menu.tsx
.
Break down of few important things in mode-toggle.jsx:
- setTheme sets the theme (light, dark, or system) in the provider so that it is accessible to the entire app.
- The tailwindCSS of Sun and Moon elements.
- Sun (scale-100 dark:scale-0): Sun icon width becomes 0 when theme is dark so that it is not visible in dark mode.
- Moon (scale-0 dark:scale-100): Moon icon width becomes 100% when theme is dark so that is is visible in dark mode.
- And vice versa.
Cool, now go ahead and try to switch the theme.
Congrats!
Oh wait, what? It is not working. Ya, it won't work because we have a little bug.
Gotcha
Right now the theme switching won't work because of a small bug.
See we are using tsx
and jsx
both in our application. But NextJs was initiated with typescript configurations so tailwind ignores our jsx files. Just add the js and jsx in tailwind config.
tailwind.config.ts
const config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx,js,jsx}',
'./components/**/*.{ts,tsx,js,jsx}',
'./app/**/*.{ts,tsx,js,jsx}',
'./src/**/*.{ts,tsx,js,jsx}',
],
...
}
And that is it.
Now our theme switching is working perfectly fine. You can check the final project at the preview link provided at top.
Your users are gonna love you for giving them a dark mode.
Reply/comment to this email/post if you have any suggestion on which feature should I build next.
If you found this useful, join the newsletter for such posts every week.
Top comments (0)