💡 회사에서 사내 사이드프로젝트와 팩토로이드에 다크모드를 적용하기 위해 , SASS 로 다크모드를 구현할 수 있는 방법을 알아봤습니다. 제가 생각하기에 가장 간편하고 효율적이라고 생각한 방법은 아래 두가지 입니다.
1. html 의 data-theme attribute 사용
2. React의 ContextAPI 사용
이 둘중에서 우리 팀에서는 별다른 의존성 주입이 필요없는 data-theme 속성을 사용하는 방법을 채택하게 됐습니다. 이글에서도, 이방식으로 다크모드를 구현 할 수 있는 방법에 대해 설명합니다.
html root 엘리먼트의 data-theme 속성을 사용해서 다크모드를 구현할 수 있습니다.
Document.documentElement API 설명 (MDN)
Next.js 의 경우에는 루트 파일인 _app.tsx 파일에서 초기 data-theme 을 지정할 수 있습니다.
import { useEffect } from "react";
import "../styles/globals.scss";
import type { AppProps } from "next/app";
import Head from "next/head";
function MyApp({ Component, pageProps }: AppProps) {
useEffect(() => {
document.documentElement.setAttribute("data-theme", "light");
}, []);
return (
<>
<Head>
<title>MeetMeet</title>
<meta charSet="utf-8"></meta>
</Head>
<Component {...pageProps} />
</>
);
}
export default MyApp;
html의 data-theme 의 속성을 사용할 경우, 별도의 세팅 필요없이 sass 변수를 사용해 구현할 수 있습니다.
두번째로, 컬러값을 아래와 같이 세팅해주고, themes.scss 파일을 만들어 html data-theme각 테마에 맞는 컬러 이름을 지정해줍니다. css 코드를 정의할 때 selector 안에 설정해서 유효범위를 만들듯이 변수도 그런 위치에 정의합니다. html[data-theme=”light”] 은 문서 트리에 루트 요소의 data-theme 속성이 “light” 일 때를 의미합니다. 따라서, 이 유효범위 내에 정의된 변수는 어디에서도 사용할 수 있는 전역 변수가 됩니다.
//color.scss
$btn-color-1: #FE016C;
$btn-color-2: #FFBD07;
$text-color-1: #232323;
$text-color-2: #fff;
$bg-color-1: #fff;
$bg-color-2: #232323;
//themes.scss
@use "./color.scss" as *;
html[data-theme="light"] {
--color-text: #{$text-color-1};
--color-background: #{$bg-color-1};
--color-btn : #{$btn-color-1};
}
html[data-theme="dark"] {
--color-text: #{$text-color-2};
--color-background: #{$bg-color-2};
--color-btn : #{$btn-color-2};
}
이제 아래와 같이 —color-background 색상 변수를 아무데서나 가져다 사용할 수 있게 되었습니다.
@use "./themes.scss" as *;
html,
body {
background-color: var(--color-background);
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
다크모드 전환을 위해서는 토글버튼이 필요합니다. input 에 onChange 이벤트를 걸어, checked 가 true 일 때는 다크모드, false 일 때는 라이트모드를 지정해주려면 아래와 같이 콜백함수를 작성하면 됩니다. 이번에도 마찬가지로 document root 엘리먼트의 data-theme 속성으로 테마를 구분합니다.
DarkModeToggle.tsx
import React from "react";
import classes from "./DarkModeToggle.module.scss";
export function DarkModeToggle() {
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.target.checked
? document.documentElement.setAttribute("data-theme", "dark")
: document.documentElement.setAttribute("data-theme", "light");
};
return (
<>
<div className={classes.toggle}>
<input type="checkbox" id="switch" name="mode" onChange={onChange} />
<label htmlFor="switch">Toggle</label>
</div>
</>
);
}
다크모드 토글 버튼의 스타일은 아래와 같이 지정해줍니다. 이번에도 마찬가지로 themes.scss 에서 지정해놓은 컬러값 변수를 가져다가 사용하기만 하면 됩니다. 이 때, 컬러값 변수들은 html 즉 문서 트리 루트 요소에 정의 되었으므로 @ use 해서 불러오지 않아도 전역적으로 사용할 수 있게 됩니다.
DarkModeToggle.module.scss
// @use "./themes.scss" as *;
// 위 @use 하지 않아도 전역적으로 사용가능함.
// @use 하면 Syntax error: Selector "html[data-theme=light]" is not
// pure (pure selectors must contain at least one local class or id) 라는 에러 발생
.toggle{
input[type=checkbox] {
height: 0;
width: 0;
visibility: hidden;
}
label {
cursor: pointer;
text-indent: -9999px;
width: 55px;
height: 30px;
background-color: var(--color-btn);
...
}
label:after {
...
}
input:checked + label {
background: var(--color-btn);
}
input:checked + label:after {
left: calc(100% - 5px);
-webkit-transform: translateX(-100%);
-moz-transform: translateX(-100%);
-ms-transform: translateX(-100%);
-o-transform: translateX(-100%);
transform: translateX(-100%);
}
}
이제 메인페이지에서 <DarkModeToggle/>
컴포넌트를 가져다 쓰면! 다크 모드 완성 ~~!
import type { NextPage } from "next";
import { DarkModeToggle } from "../components";
const Home: NextPage = () => {
return (
<div>
<DarkModeToggle />
</div>
);
};
export default Home;
하지만 위의 방식으로 구현할 경우, 사용자가 브라우저를 껐다 켰을 때, 이전에 세팅해놓은 테마 모드가 초기화되는 문제점이 있습니다. 이를 해결하기 위한 방법으로 테마 모드를 localStorage에 저장해놓고, 브라우저에 재접속한경우 해당 모드를 초기값으로 세팅되게끔 하는 방법이 있습니다.
자세한 방법은 아래 출처에서 확인하시면 됩니다. (블로그 글작성자 yijaee님 감사합니다.)
localStorage 로 다크모드 유지하는 방법
출처 : yijae 님 velog
현재 젠틀에너지 ICT 팀에서는 스타일 라이브러리로 SCSS 를 사용하고 있습니다. 처음에는 다크모드를 구현하기 위해 테마에 관련된 전역 상태를 들고, 이를 변수로 넘겨줘야? 하는 생각에 CSS in JS 라이브러리인 Styled Component 를 사용할까도 고민했었습니다.
근데 이미 기존에 만들어둔 컴포넌트들이 SCSS 로 작성되어있었기 때문에, 기존 방식에서 다크모드를 구현할 수 있는 방법을 모색했고 html 의 data-theme 속성을 사용해 전역으로 테마 컬러 변수를 사용할 수 있는 방법을 알게되었습니다.
이후, 시간이 주어진다면 ContextAPI나 다른 전역 상태관리 라이브러리를 사용하는 방법도 자세히 알아보고, 비교하는 시간을 가지려고 합니다. 드디어 팩토로이드에도 다크모드를 적용해 대시보드 다운 대시보드(?)를 구현할 수 있을 것 같습니다.