Dark Mode with Next.js, TypeScript, Styled Components and Redux Toolkit🔯🔮
Tags: react, typescript, darkmode, nextjs
Published: 2023-09-11 00:45:23

---
title: Dark Mode with Next.js, TypeScript, Styled Components and Redux Toolkit🔯🔮
published: true
description: 
tags: react, typescript, darkmode, nextjs
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-09-10 21:58 +0000
---

Hi :wave:
Thank you for taking time to read this article!

One of the most common features we implement is dark mode. However, after implementing dark mode once in a project, it's easy to forget how it was implemented because there's no opportunity to implement it for a while. 
So I decided to put together an article on the method I use most often so that everyone can check it anytime!

## Table of Contents
&nbsp;1. [Tech stack](#tech-stack)
&nbsp;2. [Step-by-step implementation](#stepbystep-implementation)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.1. [Configs, constants and types for color themes](#configs-constants-and-types-for-color-themes)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.2. [Set up Redux Toolkit](#set-up-redux-toolkit)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.3. [Utility functions to handle color theme cookie](#utility-functions-to-handle-color-theme-cookie)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.4. [Implement useColorTheme custom hook](#usecolortheme-custom-hook)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.5. [Configure RTK and Styled Components in _app.tsx](#configure-rtk-and-styled-components-in-apptsx)
&nbsp;3. [Let's implement dark mode toggle switch](#lets-implement-dark-mode-toggle-switch)

## Tech stack
The language, libraries and frameworks used for the implementation are as follows:
- Next.js
- TypeScript
- Styled Components
- Redux Toolkit
- cookies-next

## Step-by-step implementation

### Configs, constants and types for color themes
First, let's define the constants and types needed to manage the color theme.
in `const/colorTheme.ts`
```typescript
// const/colorTheme.ts

// Types of available color themes
export const colorThemeNames = [
  'light',
  'dark',
] as const;

// Can't use type ColorThemeName because of circular dependency
export const defaultColorThemeName: typeof colorThemeNames[number] = 'light';

// Cookie key for color theme
export const colorThemeCookieName = 'myAppColorTheme';

```

and

in `types/colorTheme.ts`
```typescript
// types/colorTheme.ts

import { colorThemeNames } from '../const/colorTheme';

export type ColorThemeStyle = {
  colors: {
    text: string
    background: string
    componentBackground: string
    border: string
    info: string
    infoBg: string
    danger: string
    dangerBg: string
  },
};

export type ColorThemeName = typeof colorThemeNames[number];

/**
 * Type guard for ColorThemeName
 *
 * @param {unknown} val
 * @return {*}  {val is ColorThemeName}
 */
export const isColorThemeName = (val: unknown): val is ColorThemeName => (
  colorThemeNames.includes(val as ColorThemeName)
);

```

Also we need to modify `DefaultTheme` type in `styled.d.ts` like this.
```typescript
// styled.d.ts

import 'styled-components';
import { ColorThemeStyle } from './types/colorTheme';

declare module 'styled-components' {
  export interface DefaultTheme extends ColorThemeStyle {}
}
```

It's convenient to create variables for the colors we will use.
```typescript
// const/styles/colors.tsx

export const dryadBark = '#37352f'; // light theme string color
export const white = '#ffffff'; // light theme component color
export const errigalWhite = '#f6f6f9'; // light theme background color
export const gainsboro = '#d9d9d9'; // light theme border color
export const coralRed = '#f93e3d'; // common danger color
export const translucentUnicorn = '#fcecee';
export const softPetals = '#e9f6ef';
export const vegetation = '#48cd90'; // common info color
export const stonewallGrey = '#c3c2c1';
export const astrograniteDebris = '#3b414a'; // dark theme border color
export const aswadBlack = '#141519'; // dark theme background color
export const washedBlack = '#202528'; // dark theme component background color

```
(I used [this](https://github.com/meodai/color-names) library to name these variables.)

Then declare color theme objects, default theme.
`config/styles/colorTheme.ts` 
```typescript
import { ColorThemeStyle, ColorThemeName } from '../../types/colorTheme';

// colors
import {
  dryadBark,
  white,
  errigalWhite,
  gainsboro,
  coralRed,
  vegetation,
  astrograniteDebris,
  aswadBlack,
  washedBlack,
  softPetals,
  translucentUnicorn,
} from '../../const/styles/colors';

export const defaultColorThemeName: ColorThemeName = 'light';

export const lightTheme: ColorThemeStyle = {
  colors: {
    text: dryadBark,
    background: errigalWhite,
    componentBackground: white,
    border: gainsboro,
    info: vegetation,
    infoBg: softPetals,
    danger: coralRed,
    dangerBg: translucentUnicorn,
  },
};

export const darkTheme: ColorThemeStyle = {
  colors: {
    text: white,
    background: aswadBlack,
    componentBackground: washedBlack,
    border: astrograniteDebris,
    info: vegetation,
    infoBg: softPetals,
    danger: coralRed,
    dangerBg: translucentUnicorn,
  },
};

export const themeNameStyleMap: { [key in ColorThemeName]: ColorThemeStyle } = {
  light: lightTheme,
  dark: darkTheme,
};

export const defaultColorThemeStyle = themeNameStyleMap[defaultColorThemeName];

```

### Set up Redux Toolkit
The state for dark mode must be managed globally. So let's use Redux Toolkit to manage global state.
We are going to create `colorThemeSlice`, typed `useDispatch`, typed `useSelector` and configure `store`.

`stores/slices/colorThemeSlice.ts`
```typescript
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

// "type" is needed. If no "type", circular dependency error arise
// https://stackoverflow.com/questions/63923025/how-to-fix-circular-dependencies-of-slices-with-the-rootstate
import type { RootState } from '../store';
import { ColorThemeName } from '../../types/colorTheme';
import { defaultColorThemeName } from '../../const/colorTheme';

type ColorThemeState = {
  theme: ColorThemeName
};

const initialState: ColorThemeState = {
  theme: defaultColorThemeName,
};

const colorThemeSlice = createSlice({
  name: 'colorTheme',
  initialState,
  reducers: {
    updateColorTheme: (state, action: PayloadAction<ColorThemeName>) => {
      state.theme = action.payload;
    },
  },
});

// selectors
export const selectColorTheme = (state: RootState) => state.colorTheme.theme;
export default colorThemeSlice.reducer;

// actions
export const {
  updateColorTheme,
} = colorThemeSlice.actions;

```

`stores/store.ts`
```typescript
// https://redux-toolkit.js.org/tutorials/typescript

import { configureStore } from '@reduxjs/toolkit';

// reducers
import colorThemeReducer from './slices/colorThemeSlice';

export const store = configureStore({
  reducer: {
    colorTheme: colorThemeReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

```

`stores/hooks.ts`
```typescript
// https://redux-toolkit.js.org/tutorials/typescript

import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import { RootState, AppDispatch } from './store';

export const useAppDispatch: () => AppDispatch = useDispatch;

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

```

### Utility functions to handle color theme cookie
Dark mode must persists even when the page is refreshed. To achieve this, we are going to use cookie.

`utils/cookie/colorTheme.ts`
```typescript
import { getCookie, setCookie } from 'cookies-next';
import { OptionsType } from 'cookies-next/lib/types';

import { colorThemeCookieName } from '../../const/colorTheme';

import { ColorThemeName, isColorThemeName } from '../../types/colorTheme';

/**
 * Set color theme cookie to persist color theme config
 *
 * @param {ColorThemeName} value
 * @param {OptionsType} [options]
 */
export const setColorThemeCookie = (value: ColorThemeName, options?: OptionsType) => {
  setCookie(colorThemeCookieName, value, options);
};

/**
 *
 *
 * @param {OptionsType} [options]
 * @return {string}  {string}
 */
export const getColorThemeCookie = (options?: OptionsType): string => {
  const colorThemeCookie = getCookie(colorThemeCookieName, options);
  return isColorThemeName(colorThemeCookie) ? colorThemeCookie : '';
};

```

### Implement useColorTheme custom hook
In the implementation, we'll need to switch color themes or retrieve the current color theme. Let's put those logics into a custom hook so that they can be called in every components.

`hooks/useColorTheme.ts`
```typescript
import { defaultColorThemeName } from '../const/colorTheme';

import { getColorThemeCookie, setColorThemeCookie } from '../utils/cookie/colorTheme';

import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { selectColorTheme, updateColorTheme } from '../stores/slices/colorThemeSlice';

import { themeNameStyleMap } from '../config/styles/colorThemes';

import { ColorThemeName, ColorThemeStyle, isColorThemeName } from '../types/colorTheme';

/**
 * Custom hook for handling color themes
 *
 */
const useColorTheme = () => {
  const dispatch = useAppDispatch();
  const currentColorTheme = useAppSelector(selectColorTheme);

  /**
   * Set color theme cookie and state
   *
   * @param {ColorThemeName} colorThemeName
   */
  const setColorTheme = (colorThemeName: ColorThemeName) => {
    setColorThemeCookie(colorThemeName);
    dispatch(updateColorTheme(colorThemeName));
  };

  /**
   * Initialize color theme cookie and state
   *
   * @return {void}
   */
  const initColorTheme = () => {
    const currentColorThemeCookie = getColorThemeCookie();

    if (!currentColorThemeCookie || !isColorThemeName(currentColorThemeCookie)) {
      setColorTheme(defaultColorThemeName);
      return;
    }

    dispatch(updateColorTheme(currentColorThemeCookie));
  };

  /**
   *
   *
   * @return {*} ColorTheme
   */
  const getCurrentColorThemeState = (): ColorTheme => (
    currentColorThemeState
  );

  /**
   *
   *
   * @return {*}  {ColorThemeStyle}
   */
  const getCurrentColorThemeStyle = (): ColorThemeStyle => (
    themeNameStyleMap[currentColorTheme]
  );

  return {
    setColorTheme,
    initColorTheme,
    getCurrentColorThemeState,
    getCurrentColorThemeStyle,
  };
};

export default useColorTheme;

```

### Configure RTK and Styled Components in _app.tsx
We've implemented so many functions and logic up to this point, but as it stands, we can't use them yet.
Let's edit _app.tsx to make sure Redux Toolkit and Styled Components are available for use.

`pages/_app.tsx`
```typescript
import { useEffect, ReactElement, ReactNode } from 'react';

// Next
import { NextPage } from 'next';
import { Router } from 'next/router';
import type { AppProps } from 'next/app';

// Libraries
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';

import { store } from '../stores/store';

import GlobalStyle from '../components/globalstyles';

import useColorTheme from '../hooks/useColorTheme';

// Layout configuration doc
// https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#with-typescript

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
  router: Router // Error if this property doesn't exist
};

/**
 *
 *
 * @param {AppPropsWithLayout} { Component, pageProps }
 * @return {*} JSX.Element
 */
const WithThemeProviderComponent = ({ Component, pageProps }: AppPropsWithLayout) => {
  const { initColorTheme, getCurrentColorThemeStyle } = useColorTheme();

  useEffect(() => {
    initColorTheme();
  }, []);

  return (
    <ThemeProvider theme={getCurrentColorThemeStyle()}>
      <GlobalStyle />
      <Component {...pageProps} />
    </ThemeProvider>
  );
};

const App = ({ Component, pageProps, router }: AppPropsWithLayout) => {
  const getLayout = Component.getLayout ?? ((page) => page);

  return (
    <Provider store={store}>
      {getLayout(
        <WithThemeProviderComponent
          Component={Component}
          pageProps={pageProps}
          router={router}
        />,
      )}
    </Provider>
  );
};
export default App;

```

## Let's implement dark mode toggle switch
With the implementation up to this point, we've completed the necessary preparations for switching the color theme.
Now, let's use `useColorTheme` to implement `DarkModeToggleSwitch` component (we'll skip detailed styling for now).

`globalstyles.tsx`
```typescript
...
  body {
    ...
    background-color: ${({ theme }) => theme.colors.background};
    color: ${({ theme }) => theme.colors.text};
    ...
  }

...
```

`components/DarkModeToggleSwitch.tsx`
```typescript
import { useColorTheme } from '../../../hooks/useColorTheme'


/**
 * Dark mode <-> light mode toggle switch
 * Update cookie value and global state
 *
 * @return {*} JSX.Element
 */
const DarkModeToggleSwitch = () => {
  const { setColorTheme, getCurrentColorThemeState } = useColorTheme()

  const currentColorTheme = getCurrentColorThemeState()

  const isDark = currentColorTheme === 'dark'

  const toggleDarkTheme = () => {
    isDark ? setColorTheme('light') : setColorTheme('dark')
  }

  return (
    <>
      ...
      <input type='checkbox' checked={isDark} onChange={toggleDarkTheme} />
      ...
    </>
  )
}

export default DarkModeToggleSwitch
```

---

That's all.
How was it? I hope this article was helpful for you.

There are many ways to implement color theme switching besides this article. If you have any recommended methods or articles, please share them in the comments and let me know!

And if you found this article helpful, please share it on social media!