Skip to main content
react-md

Color Scheme Provider

The ColorSchemeProvider can be used to allow the user to change the color scheme of the app to light, dark, or system. The provider can be initialized using the LocalStorageColorSchemeProvider or the useColorSchemeProvider for a custom implementation.

See the Dark Mode customization for more information around creating a dark mode for the app.

Local Storage Example

The documentation site uses a custom implementation using cookies so the website's color scheme will not match this example.

Mount the LocalStorageColorSchemeProvider near the root of the app with the defaultColorSchemeMode set to "light", "dark", or "system" and update the @react-md/core Sass configuration to match.

The current derived color scheme is "light"

The current saved color scheme is "system"

"use client";

import { Box } from "@react-md/core/box/Box";
import { Card } from "@react-md/core/card/Card";
import { SegmentedButton } from "@react-md/core/segmented-button/SegmentedButton";
import { SegmentedButtonContainer } from "@react-md/core/segmented-button/SegmentedButtonContainer";
import { LocalStorageColorSchemeProvider } from "@react-md/core/theme/LocalStorageColorSchemeProvider";
import { type ColorScheme } from "@react-md/core/theme/types";
import { useColorScheme } from "@react-md/core/theme/useColorScheme";
import { Typography } from "@react-md/core/typography/Typography";
import { cnb } from "cnbuilder";
import { type ReactElement } from "react";

import styles from "./LocalStorageExample.module.scss";

export default function LocalStorageExample(): ReactElement {
  return (
    <LocalStorageColorSchemeProvider
      // disabled so it doesn't override the website's meta tag. It is
      // recommended to keep this enabled since it fixes issues with native
      // elements while switching between color schemes
      disableMetaTag
      defaultColorScheme="system"
    >
      <Content />
    </LocalStorageColorSchemeProvider>
  );
}

const MODES: ColorScheme[] = ["light", "dark", "system"];

function Content(): ReactElement {
  const { currentColor, colorScheme, setColorScheme } = useColorScheme();

  return (
    <Card
      className={cnb(
        colorScheme === "light" && styles.light,
        colorScheme === "dark" && styles.dark,
        colorScheme === "system" && styles.system,
      )}
    >
      <Box>
        <Typography>{`The current derived color scheme is "${currentColor}"`}</Typography>
        <Typography>{`The current saved color scheme is "${colorScheme}"`}</Typography>
        <SegmentedButtonContainer>
          {MODES.map((mode) => (
            <SegmentedButton
              key={mode}
              selected={mode === colorScheme}
              onClick={() => {
                setColorScheme(mode);
              }}
            >
              {mode}
            </SegmentedButton>
          ))}
        </SegmentedButtonContainer>
      </Box>
    </Card>
  );
}

Press Enter to start editing.

@use "@react-md/core" with (
  $color-scheme: system,
  $disable-default-system-theme: true
);

.light {
  @include core.use-light-theme;
}

.dark {
  @include core.use-dark-theme;
}

.system {
  @media (prefers-color-scheme: dark) {
    @include core.use-dark-theme;
  }
}

Press Enter to start editing.

When using server side rendering, it is recommended to use cookies or an API so that the default color scheme can be set on the server preventing any screen flashing when changing from light to dark or dark to light. The code below is a simplified version of how this documentation site handles the color scheme.

src/components/CookieColorSchemeProvider.tsx
"use client";

import { type ColorScheme } from "@react-md/core/theme/types";
import { ColorSchemeProvider } from "@react-md/core/theme/useColorScheme";
import { useColorSchemeProvider } from "@react-md/core/theme/useColorSchemeProvider";
import { type UseStateSetter } from "@react-md/core/types";
import Cookies from "js-cookie";
import {
  type ReactElement,
  type ReactNode,
  useCallback,
  useState,
} from "react";

export function setCookie(name: string, value: string): void {
  const today = new Date();
  const nextYear = today.getFullYear() + 1;
  Cookies.set(name, value, {
    secure: true,
    expires: new Date(today.setFullYear(nextYear)),
    sameSite: "strict",
  });
}

export const COLOR_SCHEME_KEY = "colorScheme";

export interface CookieColorSchemeProviderProps {
  children: ReactNode;
  defaultColorScheme: ColorScheme;
}

export function CookieColorSchemeProvider(
  props: CookieColorSchemeProviderProps
): ReactElement {
  const { children, defaultColorScheme } = props;

  const [colorScheme, setColorScheme] = useState(defaultColorScheme);
  const value = useColorSchemeProvider({
    colorScheme,
    setColorScheme: useCallback<UseStateSetter<ColorScheme>>((nextOrFn) => {
      setColorScheme((prev) => {
        const next = typeof nextOrFn === "function" ? nextOrFn(prev) : nextOrFn;

        setCookie(COLOR_SCHEME_KEY, next);

        return next;
      });
    }, []),
  });

  return <ColorSchemeProvider value={value}>{children}</ColorSchemeProvider>;
}
src/app/layout.tsx
import { isColorSchemeMode } from "@react-md/core/theme/isColorScheme";
import { cookies } from "next/headers.js";
import { type ReactElement, type ReactNode } from "react";

interface RootLayoutProps {
  children: ReactNode;
}

export default function RootLayout(props: RootLayoutProps): ReactElement {
  const { children } = props;

  const instance = cookies();
  const value = instance.get(COLOR_SCHEME_KEY)?.value;
  const defaultColorSchemeMode = isColorSchemeMode(value) ? value : "system";

  return (
    <CoreProviders ssr>
      <CookieColorSchemeProvider
        defaultColorSchemeMode={defaultColorSchemeMode}
      >
        {children}
      </CookieColorSchemeProvider>
    </CoreProviders>
  );
}