Using Jotai for state management

I really like using Jotai because it is so incredibly easy to setup. Here is how I've added the dark mode theme to this blog that is created with Tailwind CSS and Next.js.

First install jotai

npm install jotai

In my layout I will import useAtom and atomWithStorage. This way when the user refreshes the page the theme will remain the same.

I have create a store.ts file in the root and added the following

import { atomWithStorage } from 'jotai/utils'

const browser = typeof window !== 'undefined'

export const themeAtom = atomWithStorage(
	'theme',
	browser && matchMedia('(prefers-color-scheme: dark)').matches
		? 'dark'
		: 'light'
)

Just to explain the store a bit. We are checking for the window because we want to grab the matchMedia part. That part will tell our site - hey, if the user hasn't already clicked on a theme button which would set the theme choice, choose the theme that matches their default system theme. Pretty cool!

And in my layout I will use this themeAtom like so:

import { PropsWithChildren, useEffect } from 'react'
import Head from 'next/head'
import clsx from 'clsx'
import { Inter } from '@next/font/google'
import { useAtom } from 'jotai'
import { themeAtom } from 'store'
import { motion } from 'framer-motion'

const inter = Inter({ subsets: ['latin'] })

const browser = typeof window !== 'undefined'

const Layout = ({ children }: PropsWithChildren) => {

	const [theme, setTheme] = useAtom(themeAtom)


	useEffect(() => {

		if (!browser) return

		document.body.classList.remove('light', 'dark')

		document.body.classList.add(theme)

	}, [theme])



	return (

		<motion.div

			animate={{

				backgroundColor: theme === 'dark' ? 'var(--primary-black)' : '#fff',

			}}

			className={clsx(

				'flex min-h-screen flex-col transition-all duration-100 ease-linear',

				inter.className

			)}

>
			<Head>

				<title>Create Next App</title>

				<meta name="description" content="Generated by create next app" />

				<meta name="viewport" content="width=device-width, initial-scale=1" />

				<link rel="icon" href="/favicon.ico" />

			</Head>

			<main className="container py-10">

				<button

					className="button my-4 w-max"

					onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
	>

					toggle theme

				</button>

				{children}

			</main>

		</motion.div>

	)

}

export default Layout

Here there is a lot going on so let me break it down. First we need to import our state from our store, which we did like so:

import { useAtom } from 'jotai'
import { themeAtom } from 'store'

const Layout = ({ children }: PropsWithChildren) => {

	const [theme, setTheme] = useAtom(themeAtom)

It works so similarly to useState! themeAtom is of type string, and we can set that type using the second argument, setTheme. Theme and setTheme can be named anything you want!

See - Jotai is so simple!

Next, we need to have a way of adding the className .dark to the body element, because we are manually toggling the theme. Without the className .dark added earlier in the HTML tree, we won't see any results appear.

So, in the Tailwind config file we need to add darkMode: 'class':

module.exports = {

	content: [

	'./pages/**/*.{js,ts,jsx,tsx}',

	'./components/**/*.{js,ts,jsx,tsx}',

	],

	darkMode: 'class',

And then we need a way to add the className="dark" to either the body or the html element. We can do this with the useEffect hook when we toggle the theme like so:


const Layout = ({ children }: PropsWithChildren) => {

	const [theme, setTheme] = useAtom(themeAtom)

	useEffect(() => {

		document.body.classList.remove('light', 'dark')

		document.body.classList.add(theme)

	}, [theme])

Now anytime we change the theme we will add the className we want ('light' or 'dark') So, we can have a button to trigger the change like so:

<button

	className="button my-4 w-max"

	onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}

>

	toggle theme

</button>

And voila! Now we can style any dark theme things using the dark variant, ie dark:bg-slate-900

Next, I'm going to write about adding Tailwind CSS Variables to your project so that you can animate your colors using something like Framer Motion. In the first example above, you might notice I am using Framer Motion to toggle the theme colors. You can learn more about how I am able to do that here: