Remix

Setting up dark mode in your Remix project.

Update tailwind.css file.

This will allow you to use dark class to apply dark mode styles.

tailwind.css
.dark,
:root[class~="dark"] {
	/* Your dark mode styles */
}

Install remix-themes.

npm install remix-themes

Create session storage.

sessions.server.tsx
import { createCookieSessionStorage } from "@remix-run/node"
import { createThemeSessionResolver } from "remix-themes"
 
const sessionStorage = createCookieSessionStorage({
	cookie: {
		name: "__remix-themes",
		// domain: 'remix.run',
		path: "/",
		httpOnly: true,
		sameSite: "lax",
		secrets: ["s3cr3t"],
		// secure: true,
	},
})
 
export const themeSessionResolver = createThemeSessionResolver(sessionStorage)

Create action to set theme.

routes/action.set-theme.tsx
import { createThemeAction } from "remix-themes"
 
import { themeSessionResolver } from "../sessions.server"
 
export const action = createThemeAction(themeSessionResolver)

Add theme provider to root layout.

root.tsx
import "./tailwind.css"
 
import { LoaderFunctionArgs } from "@remix-run/node"
import {
	Links,
	LiveReload,
	Meta,
	Outlet,
	Scripts,
	ScrollRestoration,
	useLoaderData,
} from "@remix-run/react"
import clsx from "clsx"
import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from "remix-themes"
 
import { themeSessionResolver } from "./sessions.server"
 
export async function loader({ request }: LoaderFunctionArgs) {
	const { getTheme } = await themeSessionResolver(request)
	return {
		theme: getTheme(),
	}
}
 
export default function AppWithProviders() {
	const data = useLoaderData<typeof loader>()
	return (
		<ThemeProvider specifiedTheme={data.theme} themeAction="/action/set-theme">
			<App />
		</ThemeProvider>
	)
}
 
export function App() {
	const data = useLoaderData<typeof loader>()
	const [theme] = useTheme()
	return (
		<html lang="en" className={clsx(theme)}>
			<head>
				<meta charSet="utf-8" />
				<meta name="viewport" content="width=device-width, initial-scale=1" />
				<Meta />
				<PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />
				<Links />
			</head>
			<body>
				<Outlet />
				<ScrollRestoration />
				<Scripts />
				<LiveReload />
			</body>
		</html>
	)
}

Add theme toggle component.

components/theme-toggle.tsx
import * as React from "react"
import { MoonIcon, SunIcon } from "lucide-react"
import { Theme, useTheme } from "remix-themes"
 
import { Button } from "./ui/button"
 
export function ModeToggle() {
	const [theme, setTheme] = useTheme()
 
	const toggleTheme = React.useCallback(() => {
		setTheme(theme === Theme.LIGHT ? Theme.DARK : Theme.LIGHT)
	}, [theme, setTheme])
 
	return (
		<Button onClick={toggleTheme} variant="ghost" size="icon">
			<MoonIcon className="dark:hidden" />
			<SunIcon className="hidden dark:block" />
			<span className="sr-only">Toggle theme</span>
		</Button>
	)
}