DEV Community

Cover image for Adding a Dark Theme to Next.js with Stitches
Thinh Nguyen
Thinh Nguyen

Posted on

Adding a Dark Theme to Next.js with Stitches

If you enjoy writing performant CSS with styled-components and need type-safety from Typescript when interfacing with design tokens then Stitches might be the solution right up your alley.

Stitches is a CSS-in-JS library that provides awesome DX and support for SSR frameworks like Next.js out of the box. In this tutorial, we will use Stitches to add a basic dark theme to a Next.js app with Typescript.

Initial Setup

Setting up Stitches with Next.js is easy and it works even better with the Typescript example from Vercel.

yarn create next-app --typescript nextjs-stitches-dark-theme

//then followed by
yarn add @stitches/react
Enter fullscreen mode Exit fullscreen mode

Now we can create a config file that will export all of our API functions from the library, this can be created anywhere in your project, such as the root.

// stitches.config.ts
import { createStitches } from "@stitches/react";

export const { 
  styled, 
  getCssText, 
  createTheme, 
  globalCss 
} = createStitches({ 
  theme: {
    /* ... other tokens */
    colors: {
      text: "black",
      background: "white",
    }
  }
 });

// define the dark theme using the de-constructed function
export const darkTheme = createTheme({ 
  colors: { 
    text: "white",
    background: "black"
  } 
});
Enter fullscreen mode Exit fullscreen mode

For a basic dark theme, we would want to use these theme tokens at a global level in the CSS specification, so we can use the globalCss function in the same config file to declare these styles.

// stitches.config.ts

/* ...previous code */

const GlobalStyles = globalCss({
  body: {
    //we can call the color token values with the
    //$ prefix in a string
    background: "$background", 
    color: "$text"
  }
})

//we can declare the styles here or in pages/_app.tsx
GlobalStyles();
Enter fullscreen mode Exit fullscreen mode

While that is all for configuring Stitches, you might be wondering where is the ThemeProvider that most CSS-in-JS libraries like emotion or styled-components provides. Actually, a basic installation of Stitches does not need it, you can start using the styled function by exporting it from this config file and start styling components.

Creating a Theme Toggle

But for managing multiple themes, it is highly recommended to install an additional packaged called next-themes which will allow us to toggle between themes and override the theme-specific tokens, such as colours in this case.

yarn install next-themes
Enter fullscreen mode Exit fullscreen mode

After adding the package, we can add the familiar theme provider component into our Next.js application.

// pages/_app.tsx
import type { AppProps } from "next/app";
import { ThemeProvider } from "next-themes";
import { darkTheme } from "../stitches.config";

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="system"
      value={{
        light: "light",
        dark: darkTheme.className
      }}
    >
      <Component {...pageProps} />
    </ThemeProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

And now we can create a toggle button to switch between the themes with the useTheme hook from next-themes;

// pages/index.tsx
import { styled } from "../stitches.config";
import { useTheme } from "next-themes";

const Button = styled("button", {});

export default function IndexPage() {
  const { theme, setTheme } = useTheme();
  const toggleTheme = () =>
    setTheme(theme === "light" ? "dark" : "light");

  return (
    <div>
      <h1>The current theme is {theme === "dark" ? "dark" : "light"}</h1>
      <Button onClick={toggleTheme}>Change Theme</Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And that is pretty much everything we need to have a dark mode theme with Stitches! 🎉

Additional Notes

Theming with Stitches, especially with a dark theme, requires a bit of forethought into the selection of the colour tokens since we would need to define our palettes with the same key name i.e. text is the same colour token key, but has a different value in the light and dark theme.

Luckily, the wonderful folks over at Modulz also provide a variety of colour palettes to choose from through their Radix-UI/colors package.

If this tutorial with Stitches suits your theming needs, then feel free to check out the full Codesandbox below:

Happy theming! ✹

Top comments (2)

Collapse
 
383bd03d profile image
Vlad Kovalevsky • Edited

Or even easier, without next-themes:

// stitches.config.ts

export const {
  styled,
  createTheme,
  globalCss,
  getCssText,
  theme
} = createStitches({
  theme: {
    colors: {
      white100: "rgba(255, 255, 255, 1)",
      black100: "rgba(0, 0, 0, 1)",
      background: "$white100",
      text: "$black100"
    }
  }
});

const darkTheme = createTheme({
  colors: {
    background: "$black100",
    text: "$white100"
  }
});

const globalStyles = globalCss({
  "@media (prefers-color-scheme: dark)": {
    ":root": {
      ...Object.values(darkTheme.colors).reduce((result, i) => {
        result[i.variable] = i.value;
        return result;
      }, {})
    }
  }
});

globalStyles();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
igortullio profile image
Igor TĂșllio

The $ prefix not working in globalCss