react와 tailwind를 사용하여 다크 테마 구현하기
FE/React2023. 9. 25. 21:17tailwindcss
tailwindcss
는 자주 사용하는 css를 모아놓은 라이브러리로 편리한 유틸리티 css 라이브러리입니다.
tailwindcss
에는 정말 많은 스타일들이 이미 정의가 되어있어서, 따로 css를 작성할 필요없이 class(className)에만 추가를 하면 스타일이 입혀집니다.
예를 들어, 둥근 사각형을 만든다고 했을때 아래와 같이 html 코드에 class 속성을 추가하고, border-radius
값을 정의해줘야 합니다.
<div class="rounded-rectangle"></div>
.rounded-rectangle {
width: 50px;
height: 50px;
border-radius: 0.25rem;
}
만약, tailwindcss 를 사용하게 된다면 훨씬 간단해집니다. (사용하지 않았을때 코드와 비교했을때, 훨씬 간단하게 작성할 수 있어서 생산성이 매우 높아짐)
<div class="w-[50px] h-[50px] rounded"></div>
className에 나열하여 스타일을 입히는 방식은 React 에서도 똑같이 사용할 수 있습니다.
React에서 tailwindcss 사용하기
다크모드를 적용하기 전에 tailwindcss 부터 설정해야합니다. 우선, npm(또는 yarn)을 사용하여 tailwindcss 의존성을 설치합니다.
npm install -D tailwindcss
# 또는
yarn add --dev tailwindcss
그리고, 아래 tailwindcss cli를 통해서 설정파일(tailwind.config.js
)을 생성합니다.
yarn tailwindcss init
프로젝트 root 경로에 tailwind.config.js
파일이 생성이 되는데, 아래와 같이 content
에 프로젝트 src 디렉토리의 경로를 추가합니다. (content 경로에 포함이 되어 있지 않은 경로에 위치한 파일에서 tailwind를 사용하게되면 스타일이 적용 안됨)
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
본 글에서는 typescript
기반의 React 프로젝트라서 ts
와 tsx
확장자명도 추가했습니다. 만약, typescript를 사용하지 않는다면 js와 jsx까지만 추가하셔도 됩니다.
마지막으로 tailwind css를 import 해야합니다. index.css 나 원하는 css 파일 상단에 아래 import 코드를 추가합니다.
@tailwind base;
@tailwind components;
@tailwind utilities;
여기까지 했으면 tailwindcss 설정이 완료되었습니다. 이제 JSX에 tailwind가 정의해둔 className을 추가하여 스타일을 입힐 수 있게 됐습니다.
좀 더 자세한 설정 또는 관련 내용이 궁금하시다면, 아래 공식 문서를 참고하시길 바랍니다. 잘 정리되어 있어서 쉽게 원하는 정보를 찾으실 수 있습니다.
- tailwindcss: https://tailwindcss.com/docs/installation
다크 모드 구현하기
tailwind 다크 모드 기능 활성화
tailwind에서 다크모드를 사용하려면, tailwind.config.js
에 설정이 필요합니다.
darkMode에는 2가지 속성이 있는데, 본 글에서는 수동으로 변경 할 수 있도록 하기 위해, class
를 사용했습니다.
- class: 수동으로 변경 할 수 있도록 html 태그의 className 속성에 dark가 추가되었을때, 다크 모드 스타일이 적용 됨
- media: 운영체제 설정에 따라 다크 모드 스타일이 적용 됨
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: ['./src/**/*.{js,jsx,ts,tsx}'],
//...
}
이제 아래 코드와 같이 html 태그의 className에 dark 속성이 추가되면, 다크 모드 스타일(dark:...
)이 적용됩니다. ("다크 모드 스타일"에 대해서는 글 아래에서 설명드리겠습니다)
<!-- 다크 모드 적용 안됨 -->
<html>
<head>...</head>
<body>...</body>
</html>
<!-- 다크 모드 적용 됨 -->
<html class="dark">
<head>...</head>
<body>...</body>
</html>
테마 토글 버튼 컴포넌트 구현
설정이 완료되었으니, 본격적으로 테마를 변경할 수 있는 버튼 컴포넌트를 하나 구현하도록 하겠습니다.
import React from 'react';
interface TThemeButtonProps {}
const ThemeButton = ({}: TThemeButtonProps) => {
const toggleTheme = () => {
// html 태그를 가지고 옴
const htmlEl = document.querySelector('html');
if (!htmlEl) return;
const enabledDarkMode = htmlEl.classList.contains('dark');
if (enabledDarkMode) {
// 다크모드인 경우(html 태그의 className에 dark가 있을때)
// -> className에서 dark를 제거
htmlEl.classList.remove('dark');
} else {
// 다크모드가 아닌 경우, className에서 dark를 추가
htmlEl.classList.add('dark');
}
};
return <button onClick={toggleTheme}>toggle theme</button>;
};
export default ThemeButton;
이제 버튼을 클릭해보면, 아래처럼 html 태그에 dark
가 토글이 됩니다.
이제 dark 모드일 때, 배경 색상이 달라지도록 하겠습니다. 우선 index.css에 body 태그에 tailwindcss 스타일을 추가해봅시다.
tailwind를 사용할 때 JSX는 className에 추가하면 되지만, css 파일에서는 @apply
를 이용하여 사용할 수 있습니다.
body {
@apply text-black dark:text-white dark:bg-slate-800;
}
코드에 대해 부가설명을 하자면, dark 모드가 아닌 경우에는 text-black 스타일이 설정이 되고, dark 모드인 경우에는 dark:
로 시작하는 스타일이 적용됩니다.
따라서, 위 코드는 아래처럼 스타일이 적용됩니다.
- 다크 모드가 아닌 경우: text-black (검정색 텍스트에 배경은 흰색(default))
- 다크 모드인 경우: dark:text-white dark:bg-slate-800 (흰색 텍스트에 배경은 slate 색상(어두운 계열의 색상))
응용. 다크 모드 설정 기억하기
다크 모드 적용은 완료되었으나, 다음에 사이트에 재방문했을 때도 테마가 유지되게 하려면, 브라우저 스토리지를 사용할 수 있습니다.
본 글에서는 로컬 스토리지(localStorage
)를 활용하도록 하겠습니다.
토글 버튼을 클릭했을때, localStorage에 테마 값을 저장하거나 삭제해줘야 합니다.
다크 모드인 경우에 토글 버튼을 누르면 localStorage의 "theme"값을 삭제하고, 다크 모드가 아닌 경우에 토글 버튼을 누르면, localStorage의 "theme" 값에 "dark" 를 넣어줍니다.
// ...
const LOCAL_STORAGE_KEY = {
THEME: 'theme',
} as const;
const THEME = {
LIGHT: 'light',
DARK: 'dark',
} as const;
const ThemeButton = ({}: TThemeButtonProps) => {
const toggleTheme = () => {
const htmlEl = document.querySelector('html');
if (!htmlEl) return;
const enabledDarkMode = htmlEl.classList.contains('dark');
if (enabledDarkMode) {
htmlEl.classList.remove('dark');
localStorage.removeItem(LOCAL_STORAGE_KEY.THEME);
} else {
htmlEl.classList.add('dark');
localStorage.setItem(LOCAL_STORAGE_KEY.THEME, THEME.DARK);
}
};
return <button onClick={toggleTheme}>toggle theme</button>;
};
export default ThemeButton;
여기까지 했으면 토글 버튼을 눌렀을때, localStorage에 테마 값이 저장됩니다.
이제 재방문을 했을떄, 컴포넌트가 렌더링되기 전에 localStorage로부터 저장된 테마값을 가지고 와서 테마 값에 맞는 화면을 그려주면 됩니다.
const ThemeButton = ({}: TThemeButtonProps) => {
useLayoutEffect(() => {
const theme = localStorage.getItem(LOCAL_STORAGE_KEY.THEME);
if (theme === THEME.DARK) {
document.querySelector('html')?.classList.add(THEME.DARK);
}
}, []);
// ...
}
위 코드를 보면, useEffect를 사용할 수 있겠지만, useLayoutEffect
를 사용하고 있는데, 반드시 useLayoutEffect를 사용해야 하는 이유가 있습니다.
이유는 useEffect와 useLayoutEffect 호출시기가 다르기 때문인데, useLayoutEffect는 브라우저가 화면을 그리기 전에 호출, useEffect는 브라우저가 화면을 그린 다음에 호출됩니다.
따라서, 다크 모드가 적용되기 전 화면을 그리고, useEffect가 수행되면서 다크 모드 화면을 그리게 됩니다.
직접 해보면 더 이해가 될텐데, useEffect로 구현을 해보면 아주 잠깐이지만 흰색 화면이 보였다가 다크모드가 적용되는 것을 확인할 수 있습니다.
하지만, useLayoutEffect는 성능을 저하 시킬 수 있으니 남용하지 말고 꼭 필요한 곳에서만 사용하고, 일반적인 상황에서는 useEffect를 사용하는 것이 좋습니다.
이렇게 완선된 전체 코드는 아래와 같습니다.
import React, { useLayoutEffect } from 'react';
interface TThemeButtonProps {}
const LOCAL_STORAGE_KEY = {
THEME: 'theme',
} as const;
const THEME = {
LIGHT: 'light',
DARK: 'dark',
} as const;
const ThemeButton = ({}: TThemeButtonProps) => {
useLayoutEffect(() => {
const theme = localStorage.getItem(LOCAL_STORAGE_KEY.THEME) || THEME.LIGHT;
if (theme === THEME.DARK) {
document.querySelector('html')?.classList.add(THEME.DARK);
}
}, []);
const toggleTheme = () => {
const htmlEl = document.querySelector('html');
if (!htmlEl) return;
const enabledDarkMode = htmlEl.classList.contains(THEME.DARK);
if (enabledDarkMode) {
htmlEl.classList.remove(THEME.DARK);
localStorage.removeItem(LOCAL_STORAGE_KEY.THEME);
} else {
htmlEl.classList.add(THEME.DARK);
localStorage.setItem(LOCAL_STORAGE_KEY.THEME, THEME.DARK);
}
};
return <button onClick={toggleTheme}>toggle theme</button>;
};
export default ThemeButton;
Reference
'FE > React' 카테고리의 다른 글
Vite 기반 React 프로젝트에 Tailwind CSS 적용하기 (0) | 2024.08.25 |
---|---|
Bun 설치 및 React 프로젝트 생성하기 (0) | 2024.08.24 |
React와 WebRTC를 활용하여 실시간 화상 채팅 구현하기 (1) | 2024.04.28 |
React와 Intersection Observer로 목차(TOC) 만들기 (0) | 2023.09.30 |
react를 사용해서 크롬(chrome) extension 만들기 (1) | 2023.09.24 |
IT 기술에 대한 글을 주로 작성하고, 일상 내용, 맛집/숙박/제품 리뷰 등 여러가지 주제를작성하는 블로그입니다. 티스토리 커스텀 스킨도 개발하고 있으니 관심있으신분은 Berry Skin을 검색바랍니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!