Blog

테크

Tailwind CSS 멀티 테마 구현하기 (테마 네이밍 컨벤션)

#Web#TailwindCSS#Next.js#theme#Frontend

2024-01-25

Tailwind CSS 멀티 테마 구현하기 (테마 네이밍 컨벤션)

Tailwind CSS 멀티 테마 구현하기 (테마 네이밍 컨벤션)

카테고리
테크
제품
폼스
작성자
생성일
Jan 24, 2024 06:32 AM
생성자
마지막 수정시간
Last updated January 26, 2024
상태
완료
블로그게시
게시
태그
Web
TailwindCSS
Next.js
theme
Frontend
마지막 수정자
게시자
sokkanji
블로그카테고리
tech
업로드표시날짜
Jan 25, 2024
블로그_관련제품
pomchecker
요약
Tailwind CSS 테마 네이밍 컨벤션을 이해하고 멀티 테마를 적용한 프로젝트 구현하기
블로그_이벤트
pomchecker

목차

  1. 시작하며
  1. 적용할 테마 종류
  1. Next.js 프로젝트에서 Tailwind CSS 설정하기
  1. global.css 색상 변수 정의하기 (테마 변수 네이밍 컨벤션)
  1. tailwind.config.js 색상 변수 설정하기
  1. 컴포넌트에 스타일 변수 적용하기
  1. 테마 적용하기
  1. 동적으로 테마 변경하기
  1. 새로고침을 해도 설정한 테마 유지하기
  1. 변경한 테마를 다른 페이지에도 적용하기
  1. 완성 화면
  1. 테스트
  1. 마무리하며
 
 

시작하며

안녕하세요. 이번 글에서는 Tailwind CSS를 사용한 Next.js 프로젝트에서 멀티 테마를 구현하는 방법과 테마 네이밍 컨벤션에 대해 설명을 드리고자 합니다. 쉽게 이해하실 수 있도록 플러그인을 사용하지 않은 간단한 프로젝트를 준비했고 이 프로젝트를 구현해가면서 글을 작성해 보았습니다. 글을 읽은 후에 코드를 참고하여 직접 프로젝트를 만들어보시면 Next.js 프로젝트에서 Tailwind CSS 멀티 테마 적용에 관하여 잘 이해하실 수 있을 거라고 생각됩니다.
 

적용할 테마 종류

아래와 같이 메인과 서브 색상이 각 다른 테마들을 준비했습니다.
아래 테마들을 프로젝트에 적용하고, 동적으로 변경하는 것도 구현해 보겠습니다.
 
테마 1 (기본)
메인 : 파란색, 서브 : 하늘색
 
테마 2
메인 : 회색, 서브 : 연한 회색
 
테마 3
메인 : 분홍색, 서브 : 연한 분홍색
 
 

Next.js 프로젝트에서 Tailwind CSS 설정하기

아래 공식 문서를 참고하여 프로젝트를 생성하고 Tailwind CSS를 설정해 주세요.
 
 

global.css 색상 변수 정의하기 (테마 변수 네이밍 컨벤션)

메인 색상과 서브 색상의 값들이 변경되어야 하기 때문에 색상 변수를 정의해야 합니다.
색상 변수 이름을 무엇으로 정의하면 좋을까요?
 
기본적으로 Tailwind CSS는 추상적인 단어 (ex: primary) 보다 직관적인 단어 (ex: blue) 사용하는 것을 권장하지만, 색상 값들이 변경되기 때문에 직관적인 단어로 변수 이름을 짓는다면 더 헷갈릴 가능성이 있습니다.
Tailwind CSS 에도 여러 테마를 지원해야하는 경우에는 추상적인 단어를 사용할 수 있다고 합니다.
That said, you can name your colors in Tailwind whatever you like, and if you’re working on a project that needs to support multiple themes for example, it might make sense to use more abstract names:
 
변수 이름을 정했다면, global.css 파일에서 :root를 사용해서 기본 테마를 설정합니다.
[data-theme=’pink’] 와 같이 속성 선택기를 사용하여 추가로 다른 테마들도 정의할 수 있습니다.
/* global.css */ @tailwind base; @tailwind components; @tailwind utilities; @layer base { /* 기본, 테마 1 (blue) */ :root { --color-primary: #0ea5e9; --color-secondary: #e0f2fe; } /* 테마 2 */ html[data-theme='gray'] { --color-primary: #909090; --color-secondary: #ebebebeb; } /* 테마 3 */ html[data-theme='pink'] { --color-primary: #f783ac; --color-secondary: #ffdeeb; } }
 
@layer base는 기본 스타일이 적용되는 레이어입니다. 전역 스타일, 타이포그래피, 폰트 등과 같이 기본 스타일을 정의할 때 사용합니다. 전체 프로젝트에서 사용할 공통 색상이기 때문에 base 안에 코드를 작성합니다.
 
 

tailwind.config.js 색상 변수 설정하기

global.css 에서 설정했던 대로, tailwind.config.js 에서 아래 코드와 같이 var() 를 사용하여 Tailwind CSS 커스텀 색상 변수를 정의합니다.
// tailwind.config.js import type { Config } from 'tailwindcss'; const config: Config = { theme: { extend: { colors: { primary: 'var(--color-primary)', secondary: 'var(--color-secondary)', }, }, }, }; export default config;
 
 

컴포넌트에 스타일 변수 적용하기

자유롭게 컴포넌트를 만든 후, 테마에 맞춰서 변경할 컴포넌트에 정의했던 색상 변수를 사용하여 스타일을 작성합니다.
<h2 className="text-black">멀티 테마 구현하기</h2>
<h2 className="text-primary">멀티 테마 구현하기</h2>
 
 

테마 적용하기

마지막으로 html 엘리먼트에 data-theme="gray"와 같이 theme 값을 추가하면 테마가 적용됩니다.
notion image
 
 
 

동적으로 테마 변경하기

지금까지 테마를 적용하는 법에 대해 말씀드렸습니다. 이 부분부터는 동적으로 테마 변경하는 기능을 구현하고 실제 프로젝트에서 테마 핸들링하는 법에 대해 설명드리겠습니다.
 
버튼 컴포넌트를 만들고 다음과 같이 html 데이터 속성을 변경하는 코드를 사용하여 버튼 이벤트를 추가합니다.
document.querySelector('html')?.setAttribute('data-theme', 'pink');
notion image
 
 

새로고침을 해도 설정한 테마 유지하기

여기까지 구현했을 때, 한 가지 문제가 있습니다. 테마를 저장하지 않았기 때문에 새로고침을 하거나 창을 닫고 다시 열었을 때 테마가 유지되지 않는 문제입니다. 초기값이 기본 테마 (blue) 로 들어가 있기 때문에 새로고침을 하면 항상 기본 테마 (blue)로 보일 겁니다.
 
새로고침을 하거나 창을 닫았다가 다시 열어도 테마가 유지가 되도록 하려면 프로젝트에 테마 값을 저장하는 거 말고도 다른 곳에 저장할 수 있어야 할 것 같습니다. 어떤 걸 사용해야 할까요?
 
브라우저에 데이터를 저장할 수 있고 그 데이터의 저장 만료 시간이 없는 localStorage 를 사용하면 간단하게 이 문제를 해결할 수 있습니다.
 
버튼 이벤트에 다음과 같은 localStorage 에 테마값을 저장하는 코드를 추가합니다.
localStorage.setItem('theme', 'pink');
 
버튼을 클릭할 때마다, 크롬 개발자 도구에서 localStoragetheme 값이 바뀌는 걸 확인할 수 있습니다.
notion image
 
 
getItem() 을 사용하여 localStorage 테마 값을 가져와서 테마 초기값으로 넣으면
localStorage.getItem('theme');
 
새로고침을 하거나 창을 닫고 다시 열어도 마지막에 선택했던 테마가 유지되는 것도 확인하실 수 있습니다.
notion image
 
 

변경한 테마를 다른 페이지에도 적용하기

이대로 완성을 해도 괜찮겠지만, 여러 페이지에 테마를 적용되어야하는 프로젝트가 더 많을 거라 생각됩니다.
메인 페이지에서 설정한 테마를 다른 페이지에도 적용되도록 하려면 어떻게 코드를 작성해야 할까요?
 
  1. 변경한 테마 값을 전역으로 가지고 있어야하고
  1. 전역 테마 값을 공통적으로 여러 페이지에 적용할 수 있도록 해야합니다.
 
1번은 contextAPI, 2번은 provider 를 사용하여 구현해 보겠습니다.
 

contextAPI 만들기

// contextAPI/contextAPI.ts export const ThemeContext = createContext<{ setTheme: Dispatch<SetStateAction<string | null>>; } | null>(null);
 

버튼 이벤트 수정하기

useContext 를 사용하여 위에서 만들었던 ThemeContext 를 가져옵니다.
버튼을 클릭할 때마다 해당 테마를 contextAPI 에 저장할 수 있도록 버튼 onClick 를 변경합니다.
// app/components/button.tsx const context = useContext(ThemeContext); return ( <button type="button" className="bg-[#e0f2fe] text-[#0ea5e9] p-[5px]" onClick={() => context?.setTheme('')} > Blue theme (default) </button> <button type="button" className="bg-[#e0f2fe] text-[#0ea5e9] p-[5px]" onClick={() => context?.setTheme('pink')} > Pink theme </button> ... );
 

provider 만들기

// app/provider.tsx 'use client'; import { ThemeContext } from '@/contextAPI/contextAPI'; import { useEffect, useState } from 'react'; import Loading from './loading'; interface ThemeProviderProps { children: React.ReactNode; } export default function ThemeProvider({ children }: ThemeProviderProps) { const [theme, setTheme] = useState<string | null>(null); useEffect(function initialize() { const storedTheme = localStorage.getItem('theme') ?? ''; setTheme(storedTheme); document.querySelector('html')?.setAttribute('data-theme', storedTheme); }, []); useEffect( function handleChangedTheme() { if (theme === null) { return; } document.querySelector('html')?.setAttribute('data-theme', theme); if (theme === '') { localStorage.removeItem('theme'); return; } localStorage.setItem('theme', theme); }, [theme], ); if (theme === null) { return <Loading />; } return <ThemeContext.Provider value={{ setTheme }}>{children}</ThemeContext.Provider>; }
 

provider.tsx 설명

useState theme 는 동적으로 변경된 값을 가져오고 테마 변경되었다는 것을 알기 위해 사용됩니다.
 
theme 값이 null 이면 loading 컴포넌트가 표시됩니다.
theme 값이 ‘’ 이면 기본 테마 (blue)가 적용됩니다.
기본 테마 (blue)가 선택될 경우에 data-theme 값과 localStorage 의 theme 값은 ‘’ 입니다.
 

localStorage 와 Loading 컴포넌트

useState theme 초기값을 ‘’ 이 아닌 null 로 할당하고 있고 useEffect initialize 를 통해서 localStorage theme의 값을 가져옵니다. 이렇게 작성하기보다는 처음부터 useState 를 선언할 때 localStorage.getItem() 값이나 ‘’ 를 초기값으로 선언하면 되는 거 아닌가 라고 생각하실 수 있을 것 같습니다.
 
초기값을 null 로 선언한 이유에 대해 설명드리겠습니다.
 
다음 코드는 uesState theme 값을 선언과 동시에 데이터 할당한 코드입니다.
const [theme, setTheme] = useState<string>(localStorage.getItem('theme') ?? '');
 
코드를 적용하면 다음과 같은 에러가 발생합니다.
ReferenceError: localStorage is not defined
 
에러가 발생하는 이유는
  • Next.js는 client-side 를 렌더링 하기 전 server-side 렌더링을 합니다.
  • window (window.localStorage), document 같은 브라우저 전역 객체는 client-side에서만 가져올 수 있습니다.
⇒ 페이지가 마운트되고 window 객체가 정의될 때까지 localStorage에 접근할 수 없습니다.
 
이러한 이유로 에러가 발생하면서 페이지 처음 로드했을 때 아주 잠시 기본 테마 (blue)로 보였다가 원래 테마가 적용이 되어 보였던 것입니다. localStorage 테마 값을 가져올 때까지 기본 테마 (blue)로 보이는 것을 막기 위해 Loading 처리를 했습니다. Loading 처리를 했기 때문에 처음 페이지 접속할 때 잠시 Loading 이 보일 수 있습니다.
 

provider 적용하기

layout.tsx 파일에 children 을 ThemeProvider 로 감싸줍니다.
// app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body className={inter.className}> <ThemeProvider>{children}</ThemeProvider> </body> </html> ); }
 
 

완성 화면

notion image
 
 

테스트

아래 링크를 통해 실제 구현된 프로젝트를 확인하고 코드를 확인하실 수 있습니다.
 
  • 테마 설정 프로젝트 예시
 
  • 동적으로 테마 변경하는 프로젝트 예시
 

마무리하며

Tailwind CSS 테마 네이밍 컨벤션 정의 방법을 설명하고 멀티 테마를 구현하는 방법에 관하여 간단한 프로젝트를 만들면서 설명해보았습니다. 이 글이 멀티 테마 구현하는데 있어 관련된 고민이 해소되는데 조금이나마 도움이 되었기를 바랍니다.
 
 
참고한 아티클

sokkanji

관련된 이야기

체형분석기 폼체커

제품

체형분석기 폼체커