Tailwind's class based dark mode in Next.JS
Tailwindcss
One of the best utility-based CSS frameworks I've ever used, period. It provides all the necessary CSS properties with values and we ourselves have to design. It's much more awesome than Bootstrap or ChakraUI as it doesn't push us to use the component structure.
Because Tailwind is so low-level, it never encourages you to design the same site twice. Even with the same color palette and sizing scale, it's easy to build the same component with a completely different look in the next project.
How to enable class-based dark mode for Next.Js?
In the tailwindcss docs for dark mode, it provides only a basic snippet about what to write on page load for a specific page but it doesn't provide any example or docs for the framework.
- To enable class-based dark mode we first have to override the value of the
darkMode
key fromfalse
to'class'
intailwind.config.js
file.
module.exports = {
darkMode: 'class',
// ...
}
- We have to edit the
_app.js
file to check if dark mode is applied or not before mounting the actual component to the DOM. For that, we will useuseEffect
from react just before returning the component.
In _app.js
:
import '../styles/globals.css';
import { useEffect } from 'react';
function MyApp({ Component, pageProps }) {
useEffect(() => {
if (
localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
//check if there is any key for theme in local storage and if the system color theme is dark
document.documentElement.classList.remove('light'); //OPTIONAL - remove light from the html document if any
document.documentElement.classList.add('dark'); // add dark to the <html></html> itself as <html class='dark'></html>
} else {
document.documentElement.classList.remove('dark'); // remove dark from the html document if any
document.documentElement.classList.add('light'); //OPTIONAL - add light to the <html></html> itself as <html class='light'></html>
}
},[]);
return <Component {...pageProps} />;
}
export default MyApp;
This will add class to html before component mounting.
- We also need a theme toggler button. Add our desired toggle button and we have to keep in mind that we need two buttons one for dark and one for light mode
In ThemeSwitch.jsx
:
import { useEffect, useState } from 'react';
const isDark = () => //Function that will return boolean if any of the condition is satisfied
(localStorage && localStorage.theme === 'dark') || //Condition 1 - has local storage and theme = dark in local storage is found
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches); //Condition 2 - No theme key in local storage but media color scheme is dark
const getTheme = (isDark) => (isDark ? 'dark' : 'light'); //Function to return 'dark' or 'light' string
const ThemeSwitch = () => {
const [darkMode, setDarkMode] = useState(false); //State for holding theme status
const toggleMode = () => { //onClick handler for changing theme on button press
localStorage.theme = getTheme(!darkMode); //setting up local storage theme value
if (localStorage.theme === 'dark') { // If theme is 'dark'
document.documentElement.classList.remove('light'); // remove 'light' from html class
document.documentElement.classList.add('dark'); // add 'dark' to html class
} else { // if not 'dark'
document.documentElement.classList.remove('dark'); // remove 'dark' from html class
document.documentElement.classList.add('light'); //add 'light' to html class
}
setDarkMode(!darkMode); //set dark mode state to opposite of initial value
};
useEffect(() => {
setDarkMode(isDark()); //before page mount set the value of dark mode by observing theme in local storage
}, []);
const darkModeActive =
process.browser && document.documentElement.classList.contains('dark'); // returns true if its a client and 'dark' is present in html
// process.browser is deprecated can be written as typeof window === 'undefined'
return (
<>
<button className='w-10 h-10 focus:outline-none' onClick={toggleMode}>
<span className='sr-only'>Color mode switch button</span>
{darkModeActive ? ( //switch mode icon according to html class 'dark' or 'light'
// Light Icon Svg
) : (
// Dark Icon Svg
)}
</button>
</>
);
};
export default ThemeSwitch;
Now you can add the theme switch in the navbar of your layout and can change the theme on any page.
Note: Don't forget to add dark:some-value
in the class names if you want manual control over CSS.