Tailwind's class based dark mode in Next.JS

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 from false to 'class' in tailwind.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 use useEffect 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.

light class to html

dark class to html

  • 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.