React로 다크 모드 구현과 테마 스위칭 전략
React 프로젝트에서 CSS 변수 기반 테마를 적용하고, 로컬 저장소와 브라우저 선호도를 결합한 테마 스위칭 구현 예제
목차
개요
다크 모드는 사용자 경험을 개선하는 중요한 기능이다. 구현 방식에 따라 성능, 접근성, 유지보수성이 달라진다. 이 글은 CSS 변수와 React를 결합한 실용적 전략을 설명한다. 처음 접하는 개발자도 이해할 수 있도록 단계별로 정리된다.
전략 요약
- CSS 변수로 색상 테마를 분리한다.
- 루트 엘리먼트에 데이터 속성(data-theme)을 사용해 테마를 전환한다.
- 사용자 설정은 localStorage에 저장한다.
- 브라우저의 prefers-color-scheme을 초기값으로 활용한다.
- FOUC(초기 깜박임)를 막기 위한 초기 스크립트를 적용한다.
왜 CSS 변수인가
CSS 변수는 런타임에 값 변경이 가능하다. 변수 중심 설계는 컴포넌트 스타일을 건드리지 않고 테마를 변경할 수 있게 한다. 또한 트랜지션 적용이 쉬워 부드러운 전환을 제공한다.
기본 CSS 구조
루트에 기본 변수를 선언하고 다크 테마용 변수는 data-theme 속성으로 분리한다. 핵심 색상만 변수로 관리하면 유지보수가 용이하다.
:root {
--bg: #ffffff;
--text: #111827;
--primary: #2563eb;
}
html[data-theme="dark"] {
--bg: #0b1220;
--text: #e6eef8;
--primary: #60a5fa;
}
body {
background-color: var(--bg);
color: var(--text);
transition: background-color 200ms ease, color 200ms ease;
}
React에서 테마 상태 관리
간단한 훅으로 테마 상태와 토글 함수를 만든다. 초기값은 localStorage, 없으면 prefers-color-scheme, 그 다음 기본값 순으로 결정한다.
import { useEffect, useState } from 'react'
function useTheme() {
const [theme, setTheme] = useState(() => {
try {
const stored = localStorage.getItem('theme')
if (stored) return stored
} catch (e) {}
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
return prefersDark ? 'dark' : 'light'
})
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
try { localStorage.setItem('theme', theme) } catch (e) {}
}, [theme])
const toggle = () => setTheme(prev => (prev === 'dark' ? 'light' : 'dark'))
return { theme, toggle }
}
export default useTheme
토글 컴포넌트 예제
단순한 버튼으로 구현하면 접근성 확보가 필요하다. aria-pressed 속성을 사용해 상태를 알린다.
import React from 'react'
import useTheme from './useTheme'
function ThemeToggle() {
const { theme, toggle } = useTheme()
return (
<button onClick={toggle} aria-pressed={theme === 'dark'}>
{theme === 'dark' ? '라이트 모드' : '다크 모드'}
</button>
)
}
export default ThemeToggle
초기 깜박임(FOUC) 방지
서버 사이드 렌더링이나 정적 페이지에서 클라이언트가 로드될 때 잠깐 동안 잘못된 테마가 보일 수 있다. 이를 막기 위해 인라인 스크립트로 초기 테마를 즉시 적용한다.
(function() {
try {
var stored = localStorage.getItem('theme')
if (stored) {
document.documentElement.setAttribute('data-theme', stored)
return
}
var mql = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)')
if (mql && mql.matches) document.documentElement.setAttribute('data-theme', 'dark')
} catch (e) {}
})();
추가 고려사항
- 컴포넌트 수준 스타일에는 CSS 변수 사용을 권장한다.
- 이미지나 SVG는 필터나 변수를 이용해 적응시키는 방법을 검토한다.
- 접근성 측면에서 색 대비를 확인한다.
- 서버 사이드 렌더링 환경에서는 초기 HTML에 데이터 속성 삽입을 고려한다.
맺음말
react 다크 모드 구현은 CSS 변수 중심 설계와 작은 초기 스크립트로 깔끔하게 해결된다. 테마 토글 react 구현 시 user preference와 persistence를 결합하면 사용자 경험이 개선된다. 이 패턴은 확장성이 높아 다양한 프로젝트에서 재사용 가능하다.