CSS 변수로 테마 적용하기

Written on April 24, 2022

css theme

정말 오랜만의 블로그 포스트.. 🤦🏻‍♀️ 최근 회사 업무로 앱과 웹에 다크모드를 적용한 김에 블로그에도 다크모드를 적용해 보기로 했다. 웹에 모드를 적용하는 방법 중, CSS 변수를 활용한 방법으로 구현해 보자.

CSS 변수

CSS 변수(CSS variables)는 사용자 정의 속성(CSS custom properties)이라고도 하며, CSS 속성을 미리 정의해 두고 CSS 파일 전체에서 재사용 할 수 있는 변수이다. --로 시작하며, var() 함수를 사용하여 접근할 수 있다.
아래와 같이 최상위 수준에서 text primary 컬러를 정의해 두고, 이를 필요한 곳에서 공통적으로 참조할 수 있다.

:root {
  --text-primary: #000;
}
a {
  color: var(--text-primary);
}
p {
  color: var(--text-primary);
}

CSS 변수는 특정 요소로 적용 범위를 지정할 수 있으며 필요한 경우 내부 요소에서 재정의 할 수 있다.
아래의 예에서 p 요소에 red 선택자가 있다면, 폰트 컬러는 #000가 아닌 #f00가 된다.

:root {
  --text-primary: #000;
}
p {
  color: var(--text-primary);
}
.red {
  --text-primary: #f00;
}

위에서 알아본 CSS 변수의 특성을 활용하면, 웹사이트 전역에 테마 변경을 적용할 수 있다. 아래와 같이 루트에 라이트 모드에 필요한 기본 색상을 정의하고, [data-theme="dark"]에 다크 모드에 필요한 색상을 정의한다.

:root {
  --text-primary: #000;
  --bg-primary: #fff;
}
:root[data-theme="dark"] {
  --text-primary: #fff;
  --bg-primary: #000;
}

또한, 사용자가 클릭 할 때 마다 테마를 변경하는 버튼을 만들고, onclick시 실행할 switch_theme 함수를 만들어 준다.

function setTheme(theme) {
  document.documentElement.setAttribute("data-theme", theme);
}

function switch_theme() {
  const currentTheme = document.documentElement.getAttribute("data-theme");
  setTheme(currentTheme === "dark" ? "light" : "dark");
}

아래와 같이 버튼을 클릭 할 때마다 htmldata-theme 속성이 변경되면서 테마가 적용된다.

css theme demo 1

사용자가 설정한 테마를 localstorage에 저장해 두고 재방문시 설정값을 유지하면 더욱 좋은 UX를 제공할 수 있다.

function setTheme(isDarkMode) {
  const mode = isDarkMode ? "dark" : "light";
  document.documentElement.setAttribute("data-theme", mode);
}
function switch_theme() {
  const currentTheme = document.documentElement.getAttribute("data-theme");
  const isDarkMode = !(currentTheme === "dark");
  setTheme(isDarkMode);
  localStorage.setItem("isDarkMode", isDarkMode);
}

// 웹 사이트 실행 시 localstorage에 저장된 isDarkMode 값을 참조
(function setInitialTheme() {
  const isDarkMode = JSON.parse(localStorage.getItem("isDarkMode"));
  setTheme(isDarkMode);
})();

또한, 사용자가 테마를 변경하기 전 기본 값을 운영체제의 테마 정보를 가져와서 세팅해 줄 수 도 있다. 이는 브라우저 전역 객체의 prefers-color-scheme 미디어 쿼리를 사용하여 운영체제에 설정된 테마를 감지하는 방법을 사용해 보자.

function setTheme(theme) {
  document.documentElement.setAttribute("data-theme", theme);
}
function switch_theme() {
  const currentTheme = document.documentElement.getAttribute("data-theme");
  const isDarkMode = !(currentTheme === "dark");
  setTheme(isDarkMode);
  localStorage.setItem("isDarkMode", isDarkMode);
}

(function setInitialTheme() {
  let darkMode;
  const isDarkMode = JSON.parse(localStorage.getItem("isDarkMode"));
  // localstorage에 저장된 값이 있다면 저장된 값을 참조
  if (typeof isDarkMode === "boolean") {
    darkMode = isDarkMode;
  } else {
    // localstorage에 저장된 값이 없다면 브라우저 테마 정보를 참조
    darkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
  }
  setTheme(darkMode);
})();

마지막으로 사용자가 수동으로 모드 설정을 하기 전까지 운영체제 테마 정보의 변화를 감지하여 사이트의 테마를 업데이트 해주는 코드도 추가해 보자. 사용자가 수동으로 모드 설정을 하고 나면 미디어쿼리 listener를 제거해 주는 것도 잊지 말자.

const mql = window.matchMedia("(prefers-color-scheme: dark)");

function setTheme(theme) {
  document.documentElement.setAttribute("data-theme", theme);
}
function setMediaTheme(e) {
  setTheme(e.matches);
}
function switch_theme() {
  const currentTheme = document.documentElement.getAttribute("data-theme");
  const isDarkMode = !(currentTheme === "dark");
  setTheme(isDarkMode);
  localStorage.setItem("isDarkMode", isDarkMode);
  // 브라우저 설정 값 listener 제거
  mql.removeEventListener("change", setMediaTheme);
}

(function setInitialTheme() {
  let darkMode;
  const isDarkMode = JSON.parse(localStorage.getItem("isDarkMode"));
  // localstorage에 저장된 값이 있다면 저장된 값을 참조
  if (typeof isDarkMode === "boolean") {
    darkMode = isDarkMode;
  } else {
    // localstorage에 저장된 값이 없다면 브라우저 테마 정보를 참조
    darkMode = mql.matches;
    // 브라우저 설정 값이 바뀌는지 listener 추가
    mql.addEventListener("change", setMediaTheme);
  }
  setTheme(darkMode);
})();

드디어 블로그에도 다크모드 설정을 추가했다. 🎉 CSS 변수와 prefers-color-scheme 미디어 쿼리를 활용해 좀 더 나은 사용자 경험을 제공할 수 있게 된 것 같아 뿌듯하다!

👩🏻‍💻 배우는 것을 즐기는 프론트엔드 개발자 입니다
부족한 블로그에 방문해 주셔서 감사합니다 🙇🏻‍♀️

in the process of becoming the best version of myself