Next.jsとTailwindCSSでダークモードを実装する

タイトルの通りです。Next.jsとTailwindCSSを使用してダークモードを実装してみたので、その備忘録になります。
ライブラリはテーマの切替・管理にnext-themesを使用しました。

まずはライブラリをインストール

npm install next-themes

次に_app.tsxにnext-themesのproviderを設置する。

attributeclassにするとルート要素(html)にdarkクラスが付与される。(デフォルトはdata-theme属性)

OSの環境設定に基づいてライトとダークを切り替えるようにする場合はenableSystemtrueにする。

import '@/styles/globals.scss';
import type { AppProps } from 'next/app';
import { ThemeProvider } from 'next-themes';

const MyApp = ({ Component, pageProps }: AppProps) => {
  return (
    <ThemeProvider attribute='class' enableSystem={true}>
      <Component {...pageProps} />
    </ThemeProvider>
  );
};

export default MyApp;

次にtailwind.config.jsにダークモードを利用するように設定する。指定はmediaclassがあるが、今回はclassで切替を行うのでclassを指定。
mediaを指定した場合はOSの環境設定に基づいて設定される。

module.exports = {
  darkMode: 'class',
  ...

ひとまずこれでダークモードの適用は完了で、TailwindCSSで指定してあげる場合は接頭辞にdark:をつける。

import { Html, Head, Main, NextScript } from 'next/document';

const Document = () => {
  return (
    <Html>
      <Head />
      <body className='dark:bg-primary-950 dark:text-body-white'> // ダークモード用の記述
        <Main />
        <NextScript />
      </body>
    </Html>
  );
};

export default Document;

次にダークモード用の切替ボタンを作成する。こちらもライブラリをimportして現在のthemeがどちらなのかを判定してボタンを切り替える。

import { useEffect, useState } from 'react';
import { useTheme } from 'next-themes';
import { BsSunFill, BsMoonStarsFill } from 'react-icons/bs';

export const ChangeThemeButton: React.FC = () => {
  const { theme, setTheme, resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState<boolean>(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  useEffect(() => {
    if (mounted && theme === 'system') {
      setTheme(`${resolvedTheme}`);
    }
  }, [mounted]);

  return (
    <button
      className='rounded-full p-2 transition duration-300 hover:scale-110 hover:bg-secondary-50 dark:hover:bg-secondary-900'
      type='button'
      aria-label={mounted ? (theme === 'dark' ? 'ダークモード' : 'ライトモード') : 'テーマ切り替え'}
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      {mounted && (
        <>{theme === 'dark' ? <BsSunFill size={'18px'} /> : <BsMoonStarsFill size={'1.2rem'} />}</>
      )}
    </button>
  );
};

export default ChangeThemeButton;

初回はthemevaluesystemになるので、そこを初回+themesystemの場合にresolveThemeを使用してdarklightモードかを判別して、setThemeに格納してます。

あとはthemeの値に合わせて切替を行うみたいな感じです。以上!


参考記事

https://github.com/pacocoursey/next-themes

https://zenn.dev/taka_shino/articles/a6c176da799c91

https://yokinist.me/dark-mode