【Next.js】ダークモード対応を実装してみる
初めに
今回は Next.js で作ったアプリケーションでダークモードの切り替えを行うための実装を共有したいと思います。
なお、Next.js に関しては歴が非常に浅いため、誤り等あれば指摘していただければ幸いです。
記事の対象者
- Next.js 初学者
- Next.js のダークモード対応をしたい方
使用技術
- Next.js : 13.5.6
- Material UI : 5.15.3
目的
今回は以下のようにボタンひとつでライトモード / ダークモードの切り替えを行い、それを全てのページに反映させることを目的とします。
実装
実装は以下の手順で進めていきたいと思います。
- モードの切り替え、保持をする機能を作成
- 切り替えた結果を全ページに反映
- モードの切り替えを行うボタンの作成
1. モードの切り替え、保持をする機能を作成
まずはダークモードの切り替えを行う CustomThemeProvider
を作成します。
コードは以下の通りです。
import React, {
createContext,
useState,
useMemo,
useContext,
ReactNode,
} from "react";
import {
createTheme,
ThemeProvider as MUIThemeProvider,
} from "@mui/material/styles";
const ColorModeContext = createContext({ toggleColorMode: () => {} });
interface CustomThemeProviderProps {
children: ReactNode;
}
export const CustomThemeProvider = ({ children }: CustomThemeProviderProps) => {
const [mode, setMode] = useState<"light" | "dark">("light");
// テーマを切り替える関数
const colorMode = useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
},
}),
[]
);
const theme = useMemo(
() =>
createTheme({
palette: {
mode: mode,
},
}),
[mode]
);
return (
<ColorModeContext.Provider value={colorMode}>
<MUIThemeProvider theme={theme}>{children}</MUIThemeProvider>
</ColorModeContext.Provider>
);
};
export const useCustomTheme = () => useContext(ColorModeContext);
詳しく見ていきましょう。
以下の部分では createContext
メソッドを使って、テーマモードを切り替えるためのコンテキストを作成しています。初期値は toggleColorMode
プロパティを持つオブジェクトです。
createContext
はアプリケーション全体でデータを共有するために使用され、useContext
などによってアクセスすることができます。
const ColorModeContext = createContext({ toggleColorMode: () => {} });
以下の部分では useState
を用いて mode
とその状態を変更するための setMode
を定義しています。また、初期値は light
として設定しています。
const [mode, setMode] = useState<"light" | "dark">("light");
以下の部分では useMemo
と toggleColorMode
を用いて、ダークモードの切り替えを行うための処理を記述しています。
useMemo
では引数に指定している関数の返り値をキャッシュしています。今回の場合は、toggleColorMode
が指定されていますが、この関数は返り値が void であるため、第二引数の dependencies
は空のリストになっています。
const colorMode = useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
},
}),
[]
);
以下の部分では先述の useMemo
を用いてカラーテーマを作成しています。
また、mode
を第二引数に指定することで mode
の値が変更された際に再度 createTheme
が実行されるようになっています。
const theme = useMemo(
() =>
createTheme({
palette: {
mode: mode,
},
}),
[mode]
);
以下の部分では、先ほどまでで作成した colorMode
と theme
をそれぞれ ColorModeContext.Provider
と MUIThemeProvider
に渡しています。
return (
<ColorModeContext.Provider value={colorMode}>
<MUIThemeProvider theme={theme}>{children}</MUIThemeProvider>
</ColorModeContext.Provider>
);
2. 切り替えた結果を全ページに反映
次に、先ほど作成した CustomThemeProvider
を全ページに反映させます。
コードは以下の通りです。
MyApp
で返す値を CustomThemeProvider
で囲むことで、全てのページにダークモードの変更を反映させることができます。
import "./styles/globals.css";
import type { AppProps } from "next/app";
import { CustomThemeProvider } from "@/components/theme_context";
function MyApp({ Component, pageProps }: AppProps) {
return (
<CustomThemeProvider>
<Component {...pageProps} />
</CustomThemeProvider>
);
}
export default MyApp;
3. モードの切り替えを行うボタンの作成
最後にモードの切り替えを行うボタンを作成します。
コードは以下の通りです。
import React from "react";
import { styled, useTheme } from "@mui/material/styles";
import Brightness2OutlinedIcon from '@mui/icons-material/Brightness2Outlined';
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined';
import { IconButton } from "@mui/material";
import { useCustomTheme } from "./theme_context";
const DarkModeIcon = styled(Brightness2OutlinedIcon)(({theme}) => ({
strokeWidth: 0.5,
color: "#2CD4BF"
}))
const LightModeIcon = styled(LightModeOutlinedIcon)(({theme}) => ({
strokeWidth: 0.5,
color: "#252529"
}))
export const ThemeModeButton = () => {
const theme = useTheme();
const { toggleColorMode } = useCustomTheme();
return (
<IconButton onClick={toggleColorMode} color="inherit">
{theme.palette.mode === "dark" ? (
<DarkModeIcon />
) : (
<LightModeIcon />
)}
</IconButton>
);
};
以下のコードでは、ライトモード/ダークモードの両方のアイコンを設定しています。
const DarkModeIcon = styled(Brightness2OutlinedIcon)(({theme}) => ({
strokeWidth: 0.5,
color: "#2CD4BF"
}))
const LightModeIcon = styled(LightModeOutlinedIcon)(({theme}) => ({
strokeWidth: 0.5,
color: "#252529"
}))
以下のコードではダークモードを切り替えるボタンについて、 onClick
に toggleColorMode
を指定しています。また、theme.palette.mode === "dark"
とすることで現在のモードがダークモードかどうかを判定することができます。以下では表示させるアイコンを変更しています。
<IconButton onClick={toggleColorMode} color="inherit">
{theme.palette.mode === "dark" ? (
<DarkModeIcon />
) : (
<LightModeIcon />
)}
</IconButton>
まとめ
最後まで読んでいただいてありがとうございました。
今回は Next.js × Material UI のダークモード対応について簡単に実装しました。
最近ではダークモード対応しているサイトが多いので、ご参考になれば幸いです。
誤っている点や他の実装方法等あればご指摘いただけると幸いです。
参考
useMemo
Discussion