3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js / shadcn のアプリケーションにダークモードを導入する

3
Last updated at Posted at 2025-09-26

概要

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

components/theme-provider.tsx
"use client"

import { ThemeProvider as NextThemesProvider } from "next-themes"

export function ThemeProvider({
    children,
    ...props
}: React.ComponentProps<typeof NextThemesProvider>) {
    return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

モード切替用コンポーネント

components/mode-toggle.tsx
"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を追加します。

app/layout.tsx
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を編集しておきます。

app/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/ にアクセスすると以下の画像のようになっており、上のボタンからドロップダウンでモード切替ができます。

image.png

ドロップダウンで「Dark」を選択すると、ダークモードの画面に変更できます。

image.png

感想

next-themesを利用することでダークモードの実装がかなり簡単にできるので、開発時のコスト低減が見込めます。
ダークモードは現代のWebサイトだと広く使われる機能なので、チャンスがあれば積極的に導入してみたいと思います。

参考文献

https://nextjs.org/docs/14/getting-started/installation
https://ui.shadcn.com/docs/dark-mode/next

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?