Zenn
🫠

Next.jsでライト・ダークモード切替を実装する

2024/12/01に公開

Next.jsでのライト・ダークモード切り替えについて沢山の記事を目にしました。その中でも
Dave Grayさんの動画がわかりやすかったのでこの記事でまとめてみました。

  • ライト・ダークモードの手動切り替えボタンを実装します。

https://youtu.be/7zqI4qMDdg8

[https://youtu.be/7zqI4qMDdg8]
参考にした動画です、動画時間も約10分なので気軽に見れます

環境

Next.js 15.0.3
Tailwind CSS 3.4.1

実装

tailwind.config.tsの編集

tailwind.config.tsにdarkMode: ["class"],という一文を追加します。この設定によりdark:プレフィックスを使用したスタイリングが有効になります。

tailwind.config.ts
import type { Config } from "tailwindcss";

export default {
+ darkMode: ["class"],
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        card: {
          DEFAULT: 'hsl(var(--card))',
          foreground: 'hsl(var(--card-foreground))'
        },
        popover: {
          DEFAULT: 'hsl(var(--popover))',
          foreground: 'hsl(var(--popover-foreground))'
        },
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))'
        },
        secondary: {
          DEFAULT: 'hsl(var(--secondary))',
          foreground: 'hsl(var(--secondary-foreground))'
        },
        muted: {
          DEFAULT: 'hsl(var(--muted))',
          foreground: 'hsl(var(--muted-foreground))'
        },
        accent: {
          DEFAULT: 'hsl(var(--accent))',
          foreground: 'hsl(var(--accent-foreground))'
        },
        destructive: {
          DEFAULT: 'hsl(var(--destructive))',
          foreground: 'hsl(var(--destructive-foreground))'
        },
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        chart: {
          '1': 'hsl(var(--chart-1))',
          '2': 'hsl(var(--chart-2))',
          '3': 'hsl(var(--chart-3))',
          '4': 'hsl(var(--chart-4))',
          '5': 'hsl(var(--chart-5))'
        }
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)'
      }
    }
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config;

next-themesをインストール

bash
npm install next-themes

ThemeProviderを設定

src/app/providers.tsxというファイルを作成します。

providers.tsx
'use client'

import { ThemeProvider } from 'next-themes'

export function Providers({ children }: { children: React.ReactNode }) {
  return <ThemeProvider attribute="class">{children}</ThemeProvider>
}

このProvidersをsrc/app/layout.tsxで使用します

layout.tsx
import { Providers } from "./providers";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja" suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
        suppressHydrationWarning
      >  // 👇 このProvidersを追加
        <Providers>{children}</Providers>     
      </body>
    </html>
  );
}

テーマ切り替えボタンを作成

※今回 Myna ui iconsの Lightning, LightningSlashを使いました!

darkModeSwitch.tsx
"use client";

import { Lightning, LightningSlash } from "@mynaui/icons-react";
import { useState, useEffect } from "react";
import { useTheme } from "next-themes";

export default function Switch() {
  const [mounted, setMounted] = useState(false);
  const { setTheme, resolvedTheme } = useTheme();

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

  if (!mounted) {
    return null;
  }

  if (resolvedTheme === "dark") {
    return <LightningSlash size={30} onClick={() => setTheme("light")} />;
  }

  if (resolvedTheme === "light") {
    return <Lightning size={30} onClick={() => setTheme("dark")} />;
  }
}

ボタン配置

先ほど作成した切り替えボタンを好きな場所に配置して完成です。
私はheaderの右部に配置しました。

header.tsx
 // 👇
import Switch from './darkModeSwitch'

export default function Header() {
  const navItems = [
    { name: 'Blog', href: 'blog' },
    { name: 'About', href: 'about' },
    { name: 'Contact', href: 'about#contact' },
  ]

  return (
    <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
     //(中略)
        <Sheet>
          <SheetTrigger asChild>
            <Button
              variant="ghost"
              className="mr-2 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
            >
              <Menu className="h-5 w-5" />
              <span className="sr-only">Toggle Menu</span>
            </Button>
          </SheetTrigger>
          <SheetContent side="left" className="pr-0">
            <nav className="flex flex-col gap-4">
              {navItems.map((item, index) => (
                <Link
                  key={index}
                  href={item.href}
                  className="block px-2 py-1 text-lg"
                >
                  {item.name}
                </Link>
              ))}
            </nav>
          </SheetContent>
          // 👇
          <Switch />
        </Sheet>
      </div>
    </header>
  )
}

ヘッダーの右部にライトモード時はLightningが、ダークモード時はLightningSlashが表示されます

おわりに

動画をみながらの実装になりましたが今回でNext.jsのライト・ダークモード切替を学習する事ができました。今後何か作る際は必ずライト・ダークモード切替をデフォルトで実装していきます!

Discussion

ログインするとコメントできます