Docs
Remix

Remix

A comprehensive guide detailing the process of adding dark mode support to your Remix application for enhanced user experience.

Implementing Dark Mode Functionality

Step 1: Modify Your tailwind.css File for Dark Mode Support

Include the :root[class~="dark"] selector in your tailwind.css file. This modification is crucial for enabling the application of dark mode styles using the dark class directly on your HTML element.

.dark,
:root[class~="dark"] {
  ...;
}

Step 2: Install the remix-themes Library

Initiate the process by installing the remix-themes library, which provides essential utilities for managing themes in Remix applications:

npm install remix-themes

Step 3: Establish Session Storage and a Theme Session Resolver


// You can default to 'development' if process.env.NODE_ENV is not set
const isProduction = process.env.NODE_ENV === "production";

const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "theme",
    path: "/",
    httpOnly: true,
    sameSite: "lax",
    secrets: ["s3cr3t"],
    // Set domain and secure only if in production
    ...(isProduction
      ? { domain: "your-production-domain.com", secure: true }
      : {}),
  },
});

export const themeSessionResolver = createThemeSessionResolver(sessionStorage);

Step 4: Configure Remix Themes within Your Application

Integrate the ThemeProvider component provided by remix-themes into your application's root layout file.

import clsx from "clsx";
import {
  PreventFlashOnWrongTheme,
  ThemeProvider,
  useTheme,
} from "remix-themes";

import { themeSessionResolver } from "./sessions.server";

// Return the theme from the session storage using the loader
export async function loader({ request }: LoaderFunctionArgs) {
  const { getTheme } = await themeSessionResolver(request);
  return {
    theme: getTheme(),
  };
}
// Wrap your app with ThemeProvider.
// `specifiedTheme` is the stored theme in the session storage.
// `themeAction` is the action name that's used to change the theme in the session storage.
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>
  );
}

Step 5: Implement an Action Route for Theme Switching

Create a new file at /routes/action.set-theme.ts. It is important to ensure that you pass the filename of this route to the ThemeProvider component. This specific route is responsible for handling theme change requests and storing the user's preferred theme in the session storage.

import { createThemeAction } from "remix-themes";

import { themeSessionResolver } from "./sessions.server";

export const action = createThemeAction(themeSessionResolver);

Step 6: Include a Component for Toggling the Theme

Integrate a user interface element, such as a button or switch, into your site's layout that will serve as a mode toggle, allowing users to easily switch between light and dark themes.

import { Moon, Sun } from "lucide-react";
import { Theme, useTheme } from "remix-themes";

import { Button } from "./ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "./ui/dropdown-menu";

export function ModeToggle() {
  const [, setTheme] = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" 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(Theme.LIGHT)}>
          Light
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme(Theme.DARK)}>
          Dark
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}