Published on

ทำ Dark mode ใน Next.js แบบ (App Directory)

ทำ Dark mode ใน Next.js แบบ (App Directory)
ทำ Dark mode ใน Next.js แบบ (App Directory)

อันยองงง หลังจากที่ห่างหายจากการเขียนบทความไปประมาณ 1 เดือน เนื่องจากอาการเหนื่อยๆ และสารพัดข้ออ้าง

วันนี้ฤกษ์งามยามดี เลยมานั่งเขียนบทความดีกว่า

บทความวันนี้เจมส์จะมาเขียนเกี่ยวกับการทำ Dark mode ใน Next.js ที่เป็นแบบ App Directory และใช้ TailwindCSS ด้วยครับ คิดว่าคงจะมีประโยชน์กับผู้อ่านทุกท่านที่ติดปัญหาเกี่ยวกับการทำ มาเริ่มกันเลยดีกว่า

มาเริ่มกันเลย

ขั้นแรกเดี๋ยวเราจะมาสร้างโปรเจค Next.js แบบ App Directory และใช้ TailwindCSS ขึ้นมาก่อนเน้อครับ เจมส์จะสร้างโปรเจคชื่อ next-darkmode เน้อครับ

npx create-next-app next-darkmode

เจมส์ตั้งค่าตามนี้เน้อครับ

✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … No
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … Yes
✔ What import alias would you like configured? … @/*

หลังจากที่เราสร้าง Project เปล่า ๆ ขึ้นมาเรียบร้อยแล้ว เดี๋ยวเรามาลง node_modules ชื่อ next-themes เพิ่มกันหน่อยเน้อครับ

ให้ cd เข้าไปใน project ที่สร้าง

cd next-darkmode

ติดตั้ง next-themes สำหรับเปลี่ยนเป็น Dark mode หรือ Light mode

npm install --save next-themes

ติดตั้ง @radix-ui/react-icons เพื่อจะใช้ Icon เพื่อเพิ่มความสวยงาม ในบทความนี้เจมส์จะทำให้กดที่ Icon รูปพระอาทิตย์ แล้วจะแสดงเป็น Light mode และ Icon จะเปลี่ยนเป็นรูปพระจันทร์ เมื่อกดที่ Icon ปุ่มพระจันทร์ จะเปลี่ยนเป็น Dark mode และ Icon จะเปลี่ยนเป็นรูปพระอาทิตย์

npm install --save @radix-ui/react-icons

ขั้นต่อไปเดี๋ยวเราปรับไฟล์ src/app/globals.css ให้เหลือแค่

src/app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

จากนั้นให้ปรับไฟล์ tailwind.config.ts โดยเพิ่ม darkMode: "class" เข้าไป

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

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
  darkMode: "class",
};
export default config;

จากนั้นเราจะมาเพิ่มไฟล์ src/components/SwitchTheme.tsx เพื่อเราจะใส่ปุ่มสำหรับเปลี่ยน Dark mode และ Light mode ให้กับเว็บไซต์ของเราครับ โดยจะมีโค้ดดังนี้ครับ

src/components/SwitchTheme.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";

import { MoonIcon, SunIcon } from "@radix-ui/react-icons";

const SwitchTheme = () => {
  const [mounted, setMounted] = useState(false);
  const { theme, setTheme } = useTheme();

  useEffect(() => setMounted(true));

  if (!mounted) {
    return null;
  }

  const handleSwitchTheme = () => {
    if (theme === "dark") {
      setTheme("light");
    } else {
      setTheme("dark");
    }
  };

  return (
    <button
      className="border relative mt-4 h-[1.2rem] w-[1.2rem] p-4 rounded-md hover:opacity-70"
      onClick={handleSwitchTheme}
    >
      <SunIcon className="absolute top-2 left-2 scale-0 dark:scale-100 " />
      <MoonIcon className="absolute top-2 left-2 h-[1.2rem] w-[1.2rem] scale-100 dark:scale-0" />
    </button>
  );
};

export default SwitchTheme;

โค้ดในส่วนนี้จะ Run ในฝั่ง Client เท่านั้นครับ โดยถ้าหากยังไม่ Run ฝั่ง client จะ return null กลับไป ซึ่งในส่วนของ Icon ที่แสดงจะอยู่ใน button โดยถ้าหากเป็น dark theme จะแสดง SunIcon แต่ถ้าหากเป็น light theme จะแสดง MoonIcon

เมื่อคลิกที่ button จะเช็คว่าถ้าหาก theme ปัจจุบันเป็น dark จะ setTheme ใหม่เป็น ligth

ขั้นต่อมาให้เรามาเพิ่มไฟล์ชื่อ ThemeProvider.tsx ใน src/app โดยเพิ่มโค้ดลงไปดังนี้ครับ

src/app/ThemeProvider.tsx
"use client";

import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

ในส่วนนี้เราจะเรียกใช้ ThemeProvider ของ next-themes ซึ่งเดี๋ยวเราจะเอาไปเรียกใช้ใน layout.tsx กันครับ

ปรับโค้ด layout.tsx ให้เป็นดังนี้ครับ

src/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";

import { ThemeProvider } from "./ThemeProvider";
import SwitchTheme from "@/components/SwitchTheme";

import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

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="en" suppressHydrationWarning>
      <body className={inter.className}>
        <div className="container px-4">
          <ThemeProvider
            attribute="class"
            defaultTheme="system"
            enableSystem
            disableTransitionOnChange
          >
            <SwitchTheme />
            {children}
          </ThemeProvider>
        </div>
      </body>
    </html>
  );
}

ก็คือเราเอา ThemeProvider ที่สร้างมาใส่ และ SwitchTheme ที่เป็นปุ่มเปลี่ยน Theme มาใส่ ซึ่งใน ThemeProvider เราจะส่ง Props เข้าไปดังนี้ครับคือ

  • attribute="class" คือ config ให้ในส่วนของ tag html ใช้ class แทน data-theme เวลาเราปรับ theme
  • defaultTheme="system" และ enableSystem ให้ค่าเริ่มต้นของ theme เป็น system
  • disableTransitionOnChange คือ ปรับให้ไม่ต้องมี transition ในระหว่างการเปลี่ยน theme

ในส่วน suppressHydrationWarning ที่เราเพิ่มเข้ามาในส่วนของ html คือเพื่อบอกไม่ได้แจ้งเตือน error เนื่องจากข้อมูลใน client และ server ไม่ตรงกัน

จากนั้นเราจะมาปรับในส่วนของหน้า page.tsx ให้เป็นดังนี้ครับ

src/app/page.tsx
export default function Home() {
  return (
    <div className="pt-4">
      <div>Hello world</div>
      <div className="dark:text-green-200 text-blue-800">Custom color</div>
    </div>
  );
}

โค้ดในที่นี้คือในส่วนของ Hello world จะแสดงตามค่า default ของ tailwind ที่เป็น dark mode และ light mode แต่ในส่วนของ text ที่เป็นคำว่า Custom color เราจะปรับในส่วนของ dark mode ให้แสดงสี text-green-200 แต่ถ้าเป็น light mode จะให้แสดงสี text-blue-800 ดังรูปด้านล่างครับ

ทรูปแสดง Light mode
รูปแสดง Light mode
รูปแสดง Dark mode
รูปแสดง Dark mode

เรียบร้อยแล้วครับ หวังว่าบทความนี้จะมีประโยชน์กับคุณผู้อ่าน ไม่มากก็น้อยเน้อครับ ถ้าหากบทความนี้มีส่วนไหนผิดพลาดประการใด ก็ขออภัยมา ณ​ ที่นี้ด้วยเน้อครับ