概要
Next.jsはnext-themesというライブラリを用いて比較的簡単にダークモードを実装することができます。
その実装を試した時の記録を共有します。
前提
・Node.jsのセットアップ手順は省略します
・使用するNext.jsのバージョンは14系です
・(最新は15系ですが、自分が使い慣れているのは14系なので今回はこちらにします)
手順
最初にNext.jsのインストールを行います。
今回は現在のディレクトリ直下にインストールするので、コマンドの最後にドットを入れています。
$ npx create-next-app@14 .
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
・・・
インストール出来たら、開発サーバーを起動しておきます。
$ npm run dev
shadcnをインストール、および、必要なコンポーネントをインストールしておきます。
$ npx shadcn@latest init
$ npx shadcn@latest add button
$ npx shadcn@latest add dropdown-menu
next-themesをインストールします。
$ npm i next-themes
必要なカスタムコンポーネントを作成します。
テーマ機能用provider
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
モード切替用コンポーネント
"use client";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
layoutにThemeProviderを追加します。
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}
ダークモードの変更確認のため、デフォルトのpage.tsxを編集しておきます。
import { ModeToggle } from "@/components/mode-toggle";
const HomePage = () => {
return (
<div className="mx-auto max-w-[820px]">
<div className="flex justify-center my-3">
<ModeToggle />
</div>
<div className="text-center py-10 mt-24">
<h1 className="text-4xl font-semibold ">Test title here</h1>
<p className="text-md text-muted-foreground mt-2">subtitle here</p>
</div>
<div className="space-y-10 text-lg leading-9">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Atque vel
molestiae sunt pariatur odio consequuntur praesentium id minus optio,
culpa quisquam ipsum illum iusto laudantium recusandae quia delectus
asperiores cupiditate. Lorem ipsum dolor sit amet consectetur
adipisicing elit. Architecto harum repudiandae animi, praesentium
itaque porro cum cumque corporis consequuntur obcaecati ea eos, sint
ut. Sint quia veniam accusantium libero dolore. Lorem ipsum dolor sit
amet, consectetur adipisicing elit. Est sapiente eligendi cumque
necessitatibus fugit. Natus neque debitis doloribus ad officiis
aspernatur a laborum, odit nesciunt ut, in tempora quidem id?
</p>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Atque vel
molestiae sunt pariatur odio consequuntur praesentium id minus optio,
culpa quisquam ipsum illum iusto laudantium recusandae quia delectus
asperiores cupiditate. Lorem ipsum dolor sit amet consectetur
adipisicing elit. Architecto harum repudiandae animi, praesentium
itaque porro cum cumque corporis consequuntur obcaecati ea eos, sint
ut. Sint quia veniam accusantium libero dolore. Lorem ipsum dolor sit
amet, consectetur adipisicing elit. Est sapiente eligendi cumque
necessitatibus fugit. Natus neque debitis doloribus ad officiis
aspernatur a laborum, odit nesciunt ut, in tempora quidem id?
</p>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Atque vel
molestiae sunt pariatur odio consequuntur praesentium id minus optio,
culpa quisquam ipsum illum iusto laudantium recusandae quia delectus
asperiores cupiditate. Lorem ipsum dolor sit amet consectetur
adipisicing elit. Architecto harum repudiandae animi, praesentium
itaque porro cum cumque corporis consequuntur obcaecati ea eos, sint
ut. Sint quia veniam accusantium libero dolore. Lorem ipsum dolor sit
amet, consectetur adipisicing elit. Est sapiente eligendi cumque
necessitatibus fugit. Natus neque debitis doloribus ad officiis
aspernatur a laborum, odit nesciunt ut, in tempora quidem id?
</p>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Atque vel
molestiae sunt pariatur odio consequuntur praesentium id minus optio,
culpa quisquam ipsum illum iusto laudantium recusandae quia delectus
asperiores cupiditate. Lorem ipsum dolor sit amet consectetur
adipisicing elit. Architecto harum repudiandae animi, praesentium
itaque porro cum cumque corporis consequuntur obcaecati ea eos, sint
ut. Sint quia veniam accusantium libero dolore. Lorem ipsum dolor sit
amet, consectetur adipisicing elit. Est sapiente eligendi cumque
necessitatibus fugit. Natus neque debitis doloribus ad officiis
aspernatur a laborum, odit nesciunt ut, in tempora quidem id?
</p>
</div>
</div>
);
};
export default HomePage;
http://localhost:3000/ にアクセスすると以下の画像のようになっており、上のボタンからドロップダウンでモード切替ができます。
ドロップダウンで「Dark」を選択すると、ダークモードの画面に変更できます。
感想
next-themesを利用することでダークモードの実装がかなり簡単にできるので、開発時のコスト低減が見込めます。
ダークモードは現代のWebサイトだと広く使われる機能なので、チャンスがあれば積極的に導入してみたいと思います。
参考文献
https://nextjs.org/docs/14/getting-started/installation
https://ui.shadcn.com/docs/dark-mode/next

