웹 개발

Next.js 홈페이지 만들기 - 다크 모드 (2/3)

깨비아빠0 2023. 12. 19. 11:26
728x90
반응형

 

Next.js 프로젝트 생성 글에서 만든 Next.js 보일러플레이트를 개인 홈페이지로 바꾸기 위해 몇 가지 기본적인 내용을 적용했다.

 

  1. Next.js 홈페이지 만들기 - 한글 폰트 (1/3)
  2. Next.js 홈페이지 만들기 - 다크 모드 (2/3)
  3. Next.js 홈페이지 만들기 - 기본 레이아웃 (3/3)

 

 

 

 

다크 모드 (with Tailwind)

next-themes 패키지를 설치하여 비교적 손쉽게 다크 모드를 추가할 수 있다.

 

Tailwind config 수정

tailwind는 OS 설정에 따라 다크 모드를 지원한다.

다크 모드를 직접 설정하려면, 아래와 같이 tailwind.config.ts 파일에서 darkMode 값을 "class"로 설정해야 한다.

/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'class',
  // ...
}

 

root 배경색 CSS 수정

Next.js 보일러플레이트에는 기본적으로 다크 모드가 적용되어 있는데, OS 설정을 따라 다크 모드 여부가 결정된다.

global.css를 보면 아래와 같이 "prefers-color-scheme" 미디어쿼리를 사용하여 다크 모드의 배경색을 지정한다.

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}

 

직접 다크 모드를 적용해야 하므로, 다음과 같이 미디어쿼리 대신 dark 클래스 여부에 따라 다크 모드 배경색을 지정하도록 수정한다.

@layer base {
  :root {
    --foreground-rgb: 0, 0, 0;
    --background-start-rgb: 214, 219, 220;
    --background-end-rgb: 255, 255, 255;
  }

  :root.dark {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

 

Providers 컴포넌트 추가 및 적용

providers.tsx 파일을 만들어서 next-themes의 ThemeProvider로 children을 감싸는 Providers 컴포넌트를 추가한다.

이 때, tailwind의 class 방식 다크 모드 적용을 위해 ThemeProvider에 attribute="class" 속성을 설정한다.

이에 더해 defaultTheme="dark"를 지정하면 다크 테마를 기본으로 사용하게 된다.

'use client'

import { ThemeProvider } from 'next-themes'

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

 

RootLayout의 children을 Providers로 감싸면 테마가 적용된다.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ko">
      <body className={notoSansKr.className}>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  )
}

 

다크모드 토글 UI

light/dark 모드 전환을 위한 UI를 추가한다.

 

우선 버튼에 사용할 아이콘을 위해 react icons 패키지를 설치하고, theme-mode.tsx 파일을 만들어서 다음과 같이 ThemeMode 컴포넌트를 추가한다.

'use client'

import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'
import { BsFillSunFill, BsFillMoonFill } from 'react-icons/bs'

export function ThemeMode() {
  const [ mounted, setMounted ] = useState(false)
  const { theme, setTheme } = useTheme()

  useEffect(() => { setMounted(true) }, [])
  
  const btnClass = "border rounded-md p-1"

  if (!mounted) {
    // layout shift 방지용 placeholder
    return <button className={`invisible ${btnClass}`}><BsFillSunFill /></button>
  }

  return (
    <button className={btnClass} onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      {theme === 'light' ? <BsFillSunFill /> : <BsFillMoonFill />}
    </button>
  )
}

 

그리고, RootLayout에 다음과 같이 <ThemeMode> 컴포넌트를 넣으면 화면에 다크 모드 토글 버튼이 나온다.

  return (
    <html lang="ko">
      <body className={notoSansKr.className}>
        <Providers>
          <div>
            <ThemeMode />
          </div>
          {children}
        </Providers>
      </body>
    </html>
  )

 

아래와 같이 페이지 좌측 최상단에 토글 버튼이 나오며, 버튼을 누르면 다크 모드가 적용된다.

light 모드
dark 모드

 

light/dark 모드 공통 색상 class 정의

다크 모드를 제대로 지원하려면 className에 다크 모드 시의 색상도 모두 지정해야 한다. 예를 들어, 테두리 색을 지정할 때, light 모드는 black을, dark 모드는 white를 사용하고 싶다면, className="border-black dark:border-white"와 같이 설정해야 한다. 매번 이렇게 둘 다 입력하는게 귀찮을 때에는, tailwind의 Custom Style 기능이 도움이 될 수 있다.

 

tailwind에서는 다음과 같은 방식으로 custom color를 추가할 수 있다.

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      colors: {
        'purple': '#3f3cbb',
        'midnight': '#121063',
        'metal': '#565584',
        'tahiti': '#3ab7bf',
        'silver': '#ecebff',
        'bubble-gum': '#ff77e9',
        'bermuda': '#78dcca',
      },
    },
  },
}

 

이 때, 아래와 같이 색상값에 CSS variable 사용도 가능하다. (Using CSS variables)

      colors: {
        'theme-white': "rgb(var(--theme-white) / <alpha-value>)",  
        'theme-black': "rgb(var(--theme-black) / <alpha-value>)",  
        'theme-primary': "rgb(var(--theme-primary) / <alpha-value>)",
      },

 

이제 global.css에서 root에 dark 클래스 여부에 따라 CSS variable을 정의하면, 다크 모드 여부에 따라 theme-white 등의 색상이 다르게 적용된다.

@layer base {

  :root {
    --theme-white: 255 255 255;
    --theme-black: 0 0 0;
    --theme-primary: 210 245 255;
  }

  :root.dark {
    --theme-white: 0 0 0;
    --theme-black: 255 255 255;
    --theme-primary: 34 102 255;
  }
}

 

보일러플레이트의 첫 번째 텍스트(app/page.tsx 메시지)에 border-theme-black bg-theme-primary 클래스를 적용하면, 아래와 같이 테마에 따라 다른 배경색과 테두리색이 적용된다.

 

 

참고: https://levelup.gitconnected.com/dark-mode-in-next-js-13-aab566d20baa

 

Dark Mode in Next.js 13

https://www.southampton.ac.uk/blog/wp-content/uploads/sites/27/2022/03/Dark-Mode-Hero.jpg

levelup.gitconnected.com

https://stackoverflow.com/questions/72117668/tailwind-colors-based-on-dark-mode/74574410#74574410

 

Tailwind colors based on dark mode

Is there any way to define different colors in the tailwind config so that the dark mode are applied without the dark selector? Currently I have an object like: const colors = { light: { red: ...

stackoverflow.com

 

반응형